diff --git a/.gitignore b/.gitignore index 9e2519a4..6b1886d1 100644 --- a/.gitignore +++ b/.gitignore @@ -104,3 +104,6 @@ dist # TernJS port file .tern-port + +bin/ +build/ \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..0c5ee089 --- /dev/null +++ b/Makefile @@ -0,0 +1,80 @@ +SHELL := /bin/bash +.DEFAULT_GOAL := generate + +BIN = $(CURDIR)/bin +BUILD_DIR = $(CURDIR)/build + +GOPATH = $(HOME)/go +GOBIN = $(GOPATH)/bin +GO ?= GOGC=off $(shell which go) +NODE ?= $(shell which node) +PNPM ?= $(shell which pnpm) +PKGS = $(or $(PKG),$(shell env $(GO) list ./...)) +VERSION ?= $(shell git describe --tags --always --match=v*) +SHORT_COMMIT ?= $(shell git rev-parse --short HEAD) + +PATH := $(GOBIN):$(BIN):$(PATH) + +EMBEDDED = +LDFLAGS = -w -s -X "github.com/lunogram/platform/pkg/build.version=$(VERSION)" -X "github.com/lunogram/platform/pkg/build.commit=$(SHORT_COMMIT)" + +# Printing +V ?= 0 +Q = $(if $(filter 1,$V),,@) +M = $(shell printf "\033[34;1m▶\033[0m") + +$(BUILD_DIR): + @mkdir -p $@ + +# Tools +$(BIN): + @mkdir -p $@ +$(BIN)/%: | $(BIN) ; $(info $(M) building $(@F)…) + $Q GOBIN=$(BIN) $(GO) install $(shell $(GO) list tool | grep $(@F)) + +$(EMBEDDED): + $Q mkdir -p $(shell dirname $@) + $Q touch $@ + +GOLANGCI_LINT = $(BIN)/golangci-lint +STRINGER = $(BIN)/stringer +MINIMOCK = $(BIN)/minimock +OAPI_CODEGEN = $(BIN)/oapi-codegen + +TOOLCHAIN = $(STRINGER) $(MINIMOCK) $(OAPI_CODEGEN) + +# Targets +.PHONY: lint +lint: | $(EMBEDDED) $(GOLANGCI_LINT) $(BUF) ; $(info $(M) running linters…) @ ## Run the project linters + $Q $(GOLANGCI_LINT) run --max-issues-per-linter 10 --timeout 5m + +.PHONY: test +test: | $(EMBEDDED) ; $(info $(M) running tests) @ ## Run all tests + $Q $(GO) test $(PKGS) -timeout 300s -race -count 1 + +.PHONY: test-short +test-short: | $(EMBEDDED) ; $(info $(M) running short tests) @ ## Run all short tests + $Q $(GO) test $(PKGS) -timeout 120s -race -count 1 -short + +.PHONY: fmt +fmt: | $(EMBEDDED) ; $(info $(M) running go fmt…) @ ## Run gofmt on all source files + $Q $(GO) fmt $(PKGS) + +.PHONY: generate +generate: | $(EMBEDDED) $(TOOLCHAIN) ; $(info $(M) running go generate…) @ ## Run gogenerate on all source files + $Q $(GO) generate $(PKGS) + $Q $(MAKE) fmt + +.PHONY: clean +clean: ; $(info $(M) cleaning…) @ ## Cleanup everything + @rm -rf $(BIN) + @rm -rf $(BUILD) + @find . -name '*_mock_test.go' -exec rm -r {} \; + @find . -name '*_string.go' -exec rm -r {} \; + @find . -name '*_gen.go' -exec rm -r {} \; + @find . -name '*.sql.go' -exec rm -r {} \; + +.PHONY: help +help: + @grep -E '^[ a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \ + awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-15s\033[0m %s\n", $$1, $$2}' diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 26fcd0e1..00000000 --- a/docker-compose.yml +++ /dev/null @@ -1,201 +0,0 @@ -version: '3.4' -services: - postgres: - image: postgres:18 - platform: linux/x86_64 - environment: - POSTGRES_DB: ${DB_DATABASE} - POSTGRES_USER: ${DB_USERNAME} - POSTGRES_PASSWORD: ${DB_PASSWORD} - expose: - - 5432 - volumes: - - postgres_data:/var/lib/postgresql/data - restart: unless-stopped - healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${DB_USERNAME} -d ${DB_DATABASE}"] - interval: 5s - timeout: 5s - retries: 30 - redis: - image: redis - expose: - - 6379 - restart: always - volumes: - - redis_data:/data - api: - platform: linux/x86_64 - image: "ghcr.io/lunogram/api:${IMAGE_VERSION:-latest}" - restart: always - healthcheck: - test: wget --no-verbose --tries=1 --spider http://localhost:8080/api/health || exit 1 - interval: 5s - retries: 30 - start_period: 20s - timeout: 10s - ports: - - 8080:8080 - links: - - mysql:mysql - - redis:redis - depends_on: - mysql: - condition: service_healthy - clickhouse: - condition: service_healthy - redis: - condition: service_started - environment: - NODE_ENV: ${NODE_ENV} - BASE_URL: ${BASE_URL} - RUNNER: 'api' - APP_SECRET: ${APP_SECRET} - PORT: 8080 - LOG_LEVEL: ${LOG_LEVEL:-info} - LOG_COMPILED_MESSAGE: ${LOG_COMPILED_MESSAGE:-true} - DB_CLIENT: ${DB_CLIENT} - DB_HOST: mysql - DB_USERNAME: ${DB_USERNAME} - DB_PASSWORD: ${DB_PASSWORD} - DB_PORT: ${DB_PORT} - DB_DATABASE: ${DB_DATABASE} - CLICKHOUSE_URL: ${CLICKHOUSE_URL} - CLICKHOUSE_USERNAME: ${CLICKHOUSE_USERNAME} - CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD} - CLICKHOUSE_DATABASE: ${CLICKHOUSE_DATABASE} - STORAGE_DRIVER: ${STORAGE_DRIVER} - STORAGE_BASE_URL: ${STORAGE_BASE_URL} - STORAGE_S3_BUCKET: ${STORAGE_S3_BUCKET} - STORAGE_S3_ENDPOINT: ${STORAGE_S3_ENDPOINT} - STORAGE_S3_FORCE_PATH_STYLE: ${STORAGE_S3_FORCE_PATH_STYLE} - AWS_S3_BUCKET: ${AWS_S3_BUCKET} - QUEUE_DRIVER: ${QUEUE_DRIVER} - REDIS_HOST: ${REDIS_HOST} - REDIS_PORT: ${REDIS_PORT} - REDIS_TLS: ${REDIS_TLS} - REDIS_CONCURRENCY: ${REDIS_CONCURRENCY} - REDIS_USERNAME: ${REDIS_USERNAME} - REDIS_PASSWORD: ${REDIS_PASSWORD} - AWS_SQS_QUEUE_URL: ${AWS_SQS_QUEUE_URL} - AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID} - AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY} - AWS_REGION: ${AWS_REGION} - AUTH_DRIVER: ${AUTH_DRIVER} - AUTH_BASIC_EMAIL: ${AUTH_BASIC_EMAIL} - AUTH_BASIC_PASSWORD: ${AUTH_BASIC_PASSWORD} - AUTH_BASIC_NAME: ${AUTH_BASIC_NAME} - AUTH_SAML_CALLBACK_URL: ${AUTH_SAML_CALLBACK_URL} - AUTH_SAML_ENTRY_POINT_URL: ${AUTH_SAML_ENTRY_POINT_URL} - AUTH_SAML_ISSUER: ${AUTH_SAML_ISSUER} - AUTH_SAML_CERT: ${AUTH_SAML_CERT} - AUTH_SAML_IS_AUTHN_SIGNED: ${AUTH_SAML_IS_AUTHN_SIGNED} - AUTH_SAML_NAME: ${AUTH_SAML_NAME} - AUTH_OPENID_ISSUER_URL: ${AUTH_OPENID_ISSUER_URL} - AUTH_OPENID_CLIENT_ID: ${AUTH_OPENID_CLIENT_ID} - AUTH_OPENID_CLIENT_SECRET: ${AUTH_OPENID_CLIENT_SECRET} - AUTH_OPENID_REDIRECT_URI: ${AUTH_OPENID_REDIRECT_URI} - AUTH_OPENID_DOMAIN_WHITELIST: ${AUTH_OPENID_DOMAIN_WHITELIST} - AUTH_OPENID_RESPONSE_TYPES: ${AUTH_OPENID_RESPONSE_TYPES} - AUTH_OPENID_NAME: ${AUTH_OPENID_NAME} - AUTH_GOOGLE_ISSUER_URL: ${AUTH_GOOGLE_ISSUER_URL} - AUTH_GOOGLE_CLIENT_ID: ${AUTH_GOOGLE_CLIENT_ID} - AUTH_GOOGLE_CLIENT_SECRET: ${AUTH_GOOGLE_CLIENT_SECRET} - AUTH_GOOGLE_NAME: ${AUTH_GOOGLE_NAME} - ERROR_DRIVER: ${ERROR_DRIVER} - ERROR_BUGSNAG_API_KEY: ${ERROR_BUGSNAG_API_KEY} - ERROR_SENTRY_DSN: ${ERROR_SENTRY_DSN} - TRACKING_LINK_WRAP: ${TRACKING_LINK_WRAP} - TRACKING_DEEPLINK_MIRROR_URL: ${TRACKING_DEEPLINK_MIRROR_URL} - volumes: - - uploads:/usr/src/app/public/uploads - worker: - platform: linux/x86_64 - image: "ghcr.io/lunogram/api:${IMAGE_VERSION:-latest}" - restart: always - links: - - mysql:mysql - - redis:redis - depends_on: - mysql: - condition: service_healthy - redis: - condition: service_started - api: - condition: service_healthy - environment: - NODE_ENV: ${NODE_ENV} - BASE_URL: ${BASE_URL} - RUNNER: 'worker' - APP_SECRET: ${APP_SECRET} - LOG_LEVEL: ${LOG_LEVEL:-info} - LOG_COMPILED_MESSAGE: ${LOG_COMPILED_MESSAGE:-true} - DB_CLIENT: ${DB_CLIENT} - DB_HOST: mysql - DB_USERNAME: ${DB_USERNAME} - DB_PASSWORD: ${DB_PASSWORD} - DB_PORT: ${DB_PORT} - DB_DATABASE: ${DB_DATABASE} - CLICKHOUSE_URL: ${CLICKHOUSE_URL} - CLICKHOUSE_USERNAME: ${CLICKHOUSE_USERNAME} - CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD} - CLICKHOUSE_DATABASE: ${CLICKHOUSE_DATABASE} - STORAGE_DRIVER: ${STORAGE_DRIVER} - STORAGE_BASE_URL: ${STORAGE_BASE_URL} - STORAGE_S3_BUCKET: ${STORAGE_S3_BUCKET} - STORAGE_S3_ENDPOINT: ${STORAGE_S3_ENDPOINT} - STORAGE_S3_FORCE_PATH_STYLE: ${STORAGE_S3_FORCE_PATH_STYLE} - AWS_S3_BUCKET: ${AWS_S3_BUCKET} - QUEUE_DRIVER: ${QUEUE_DRIVER} - REDIS_HOST: ${REDIS_HOST} - REDIS_PORT: ${REDIS_PORT} - REDIS_TLS: ${REDIS_TLS} - REDIS_CONCURRENCY: ${REDIS_CONCURRENCY} - REDIS_USERNAME: ${REDIS_USERNAME} - REDIS_PASSWORD: ${REDIS_PASSWORD} - AWS_SQS_QUEUE_URL: ${AWS_SQS_QUEUE_URL} - AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID} - AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY} - AWS_REGION: ${AWS_REGION} - AUTH_DRIVER: ${AUTH_DRIVER} - AUTH_BASIC_EMAIL: ${AUTH_BASIC_EMAIL} - AUTH_BASIC_PASSWORD: ${AUTH_BASIC_PASSWORD} - AUTH_BASIC_NAME: ${AUTH_BASIC_NAME} - AUTH_SAML_CALLBACK_URL: ${AUTH_SAML_CALLBACK_URL} - AUTH_SAML_ENTRY_POINT_URL: ${AUTH_SAML_ENTRY_POINT_URL} - AUTH_SAML_ISSUER: ${AUTH_SAML_ISSUER} - AUTH_SAML_CERT: ${AUTH_SAML_CERT} - AUTH_SAML_IS_AUTHN_SIGNED: ${AUTH_SAML_IS_AUTHN_SIGNED} - AUTH_SAML_NAME: ${AUTH_SAML_NAME} - AUTH_OPENID_ISSUER_URL: ${AUTH_OPENID_ISSUER_URL} - AUTH_OPENID_CLIENT_ID: ${AUTH_OPENID_CLIENT_ID} - AUTH_OPENID_CLIENT_SECRET: ${AUTH_OPENID_CLIENT_SECRET} - AUTH_OPENID_REDIRECT_URI: ${AUTH_OPENID_REDIRECT_URI} - AUTH_OPENID_DOMAIN_WHITELIST: ${AUTH_OPENID_DOMAIN_WHITELIST} - AUTH_OPENID_NAME: ${AUTH_OPENID_NAME} - AUTH_GOOGLE_ISSUER_URL: ${AUTH_GOOGLE_ISSUER_URL} - AUTH_GOOGLE_CLIENT_ID: ${AUTH_GOOGLE_CLIENT_ID} - AUTH_GOOGLE_CLIENT_SECRET: ${AUTH_GOOGLE_CLIENT_SECRET} - AUTH_GOOGLE_NAME: ${AUTH_GOOGLE_NAME} - ERROR_DRIVER: ${ERROR_DRIVER} - ERROR_BUGSNAG_API_KEY: ${ERROR_BUGSNAG_API_KEY} - ERROR_SENTRY_DSN: ${ERROR_SENTRY_DSN} - TRACKING_LINK_WRAP: ${TRACKING_LINK_WRAP} - TRACKING_DEEPLINK_MIRROR_URL: ${TRACKING_DEEPLINK_MIRROR_URL} - ui: - platform: linux/x86_64 - image: "ghcr.io/lunogram/ui:${LUNOGRAM_VERSION:-latest}" - depends_on: - api: - condition: service_healthy - environment: - API_BASE_URL: ${API_BASE_URL} - ports: - - ${UI_PORT}:3000 -volumes: - postgres_data: - driver: local - redis_data: - driver: local - uploads: - driver: local diff --git a/go.mod b/go.mod new file mode 100644 index 00000000..7d8c2819 --- /dev/null +++ b/go.mod @@ -0,0 +1,254 @@ +module github.com/lunogram/platform + +go 1.25.1 + +require ( + github.com/MicahParks/keyfunc/v3 v3.7.0 + github.com/caarlos0/env/v10 v10.0.0 + github.com/cloudproud/graceful v1.1.1 + github.com/getkin/kin-openapi v0.133.0 + github.com/go-chi/chi/v5 v5.2.3 + github.com/golang-jwt/jwt/v5 v5.3.0 + github.com/golang-migrate/migrate/v4 v4.19.0 + github.com/google/uuid v1.6.0 + github.com/jackc/pgx/v5 v5.7.5 + github.com/jmoiron/sqlx v1.3.5 + github.com/lib/pq v1.10.9 + github.com/oapi-codegen/nethttp-middleware v1.1.2 + github.com/oapi-codegen/runtime v1.1.1 + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 + go.opentelemetry.io/otel v1.38.0 + go.uber.org/zap v1.27.0 +) + +require ( + 4d63.com/gocheckcompilerdirectives v1.3.0 // indirect + 4d63.com/gochecknoglobals v0.2.2 // indirect + github.com/4meepo/tagalign v1.4.2 // indirect + github.com/Abirdcfly/dupword v0.1.3 // indirect + github.com/Antonboom/errname v1.0.0 // indirect + github.com/Antonboom/nilnil v1.0.1 // indirect + github.com/Antonboom/testifylint v1.5.2 // indirect + github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c // indirect + github.com/Crocmagnon/fatcontext v0.7.1 // indirect + github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 // indirect + github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.1 // indirect + github.com/Masterminds/semver/v3 v3.3.0 // indirect + github.com/MicahParks/jwkset v0.11.0 // indirect + github.com/OpenPeeDeeP/depguard/v2 v2.2.1 // indirect + github.com/alecthomas/go-check-sumtype v0.3.1 // indirect + github.com/alexkohler/nakedret/v2 v2.0.5 // indirect + github.com/alexkohler/prealloc v1.0.0 // indirect + github.com/alingse/asasalint v0.0.11 // indirect + github.com/alingse/nilnesserr v0.1.2 // indirect + github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect + github.com/ashanbrown/forbidigo v1.6.0 // indirect + github.com/ashanbrown/makezero v1.2.0 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/bkielbasa/cyclop v1.2.3 // indirect + github.com/blizzy78/varnamelen v0.8.0 // indirect + github.com/bombsimon/wsl/v4 v4.5.0 // indirect + github.com/breml/bidichk v0.3.2 // indirect + github.com/breml/errchkjson v0.4.0 // indirect + github.com/butuzov/ireturn v0.3.1 // indirect + github.com/butuzov/mirror v1.3.0 // indirect + github.com/catenacyber/perfsprint v0.8.2 // indirect + github.com/ccojocar/zxcvbn-go v1.0.2 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/charithe/durationcheck v0.0.10 // indirect + github.com/chavacava/garif v0.1.0 // indirect + github.com/ckaznocha/intrange v0.3.0 // indirect + github.com/curioswitch/go-reassign v0.3.0 // indirect + github.com/daixiang0/gci v0.13.5 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/denis-tingaikin/go-header v0.5.0 // indirect + github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 // indirect + github.com/ettle/strcase v0.2.0 // indirect + github.com/fatih/color v1.18.0 // indirect + github.com/fatih/structtag v1.2.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/firefart/nonamedreturns v1.0.5 // indirect + github.com/fsnotify/fsnotify v1.8.0 // indirect + github.com/fzipp/gocyclo v0.6.0 // indirect + github.com/ghostiam/protogetter v0.3.9 // indirect + github.com/go-critic/go-critic v0.12.0 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/swag v0.23.0 // indirect + github.com/go-sql-driver/mysql v1.9.3 // indirect + github.com/go-toolsmith/astcast v1.1.0 // indirect + github.com/go-toolsmith/astcopy v1.1.0 // indirect + github.com/go-toolsmith/astequal v1.2.0 // indirect + github.com/go-toolsmith/astfmt v1.1.0 // indirect + github.com/go-toolsmith/astp v1.1.0 // indirect + github.com/go-toolsmith/strparse v1.1.0 // indirect + github.com/go-toolsmith/typep v1.1.0 // indirect + github.com/go-viper/mapstructure/v2 v2.2.1 // indirect + github.com/go-xmlfmt/xmlfmt v1.1.3 // indirect + github.com/gobwas/glob v0.2.3 // indirect + github.com/gofrs/flock v0.12.1 // indirect + github.com/gojuno/minimock/v3 v3.4.7 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32 // indirect + github.com/golangci/go-printf-func-name v0.1.0 // indirect + github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d // indirect + github.com/golangci/golangci-lint v1.64.8 // indirect + github.com/golangci/misspell v0.6.0 // indirect + github.com/golangci/plugin-module-register v0.1.1 // indirect + github.com/golangci/revgrep v0.8.0 // indirect + github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed // indirect + github.com/google/go-cmp v0.7.0 // indirect + github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e // indirect + github.com/gordonklaus/ineffassign v0.1.0 // indirect + github.com/gorilla/mux v1.8.1 // indirect + github.com/gostaticanalysis/analysisutil v0.7.1 // indirect + github.com/gostaticanalysis/comment v1.5.0 // indirect + github.com/gostaticanalysis/forcetypeassert v0.2.0 // indirect + github.com/gostaticanalysis/nilerr v0.1.1 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-immutable-radix/v2 v2.1.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/go-version v1.7.0 // indirect + github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/hexdigest/gowrap v1.4.3 // indirect + github.com/hexops/gotextdiff v1.0.3 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect + github.com/jgautheron/goconst v1.7.1 // indirect + github.com/jingyugao/rowserrcheck v1.1.1 // indirect + github.com/jjti/go-spancheck v0.6.4 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/julz/importas v0.2.0 // indirect + github.com/karamaru-alpha/copyloopvar v1.2.1 // indirect + github.com/kisielk/errcheck v1.9.0 // indirect + github.com/kkHAIKE/contextcheck v1.1.6 // indirect + github.com/kulti/thelper v0.6.3 // indirect + github.com/kunwardeep/paralleltest v1.0.10 // indirect + github.com/lasiar/canonicalheader v1.1.2 // indirect + github.com/ldez/exptostd v0.4.2 // indirect + github.com/ldez/gomoddirectives v0.6.1 // indirect + github.com/ldez/grignotin v0.9.0 // indirect + github.com/ldez/tagliatelle v0.7.1 // indirect + github.com/ldez/usetesting v0.4.2 // indirect + github.com/leonklingele/grouper v1.1.2 // indirect + github.com/macabu/inamedparam v0.1.3 // indirect + github.com/magiconair/properties v1.8.6 // indirect + github.com/mailru/easyjson v0.9.0 // indirect + github.com/maratori/testableexamples v1.0.0 // indirect + github.com/maratori/testpackage v1.1.1 // indirect + github.com/matoous/godox v1.1.0 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect + github.com/mgechev/revive v1.7.0 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect + github.com/moricho/tparallel v0.3.2 // indirect + github.com/nakabonne/nestif v0.3.1 // indirect + github.com/nishanths/exhaustive v0.12.0 // indirect + github.com/nishanths/predeclared v0.2.2 // indirect + github.com/nunnatsa/ginkgolinter v0.19.1 // indirect + github.com/oapi-codegen/oapi-codegen/v2 v2.5.1 // indirect + github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect + github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect + github.com/olekukonko/tablewriter v0.0.5 // indirect + github.com/pelletier/go-toml v1.9.5 // indirect + github.com/pelletier/go-toml/v2 v2.2.3 // indirect + github.com/perimeterx/marshmallow v1.1.5 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/polyfloyd/go-errorlint v1.7.1 // indirect + github.com/prometheus/client_golang v1.12.1 // indirect + github.com/prometheus/client_model v0.2.0 // indirect + github.com/prometheus/common v0.32.1 // indirect + github.com/prometheus/procfs v0.7.3 // indirect + github.com/quasilyte/go-ruleguard v0.4.3-0.20240823090925-0fe6f58b47b1 // indirect + github.com/quasilyte/go-ruleguard/dsl v0.3.22 // indirect + github.com/quasilyte/gogrep v0.5.0 // indirect + github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect + github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect + github.com/raeperd/recvcheck v0.2.0 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/rogpeppe/go-internal v1.14.1 // indirect + github.com/ryancurrah/gomodguard v1.3.5 // indirect + github.com/ryanrolds/sqlclosecheck v0.5.1 // indirect + github.com/sanposhiho/wastedassign/v2 v2.1.0 // indirect + github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 // indirect + github.com/sashamelentyev/interfacebloat v1.1.0 // indirect + github.com/sashamelentyev/usestdlibvars v1.28.0 // indirect + github.com/securego/gosec/v2 v2.22.2 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/sivchari/containedctx v1.0.3 // indirect + github.com/sivchari/tenv v1.12.1 // indirect + github.com/sonatard/noctx v0.1.0 // indirect + github.com/sourcegraph/go-diff v0.7.0 // indirect + github.com/speakeasy-api/jsonpath v0.6.0 // indirect + github.com/speakeasy-api/openapi-overlay v0.10.2 // indirect + github.com/spf13/afero v1.12.0 // indirect + github.com/spf13/cast v1.5.0 // indirect + github.com/spf13/cobra v1.9.1 // indirect + github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/spf13/pflag v1.0.7 // indirect + github.com/spf13/viper v1.12.0 // indirect + github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect + github.com/stbenjam/no-sprintf-host-port v0.2.0 // indirect + github.com/stretchr/objx v0.5.2 // indirect + github.com/stretchr/testify v1.11.1 // indirect + github.com/subosito/gotenv v1.4.1 // indirect + github.com/tdakkota/asciicheck v0.4.1 // indirect + github.com/tetafro/godot v1.5.0 // indirect + github.com/timakin/bodyclose v0.0.0-20241017074812-ed6a65f985e3 // indirect + github.com/timonwong/loggercheck v0.10.1 // indirect + github.com/tomarrell/wrapcheck/v2 v2.10.0 // indirect + github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect + github.com/ultraware/funlen v0.2.0 // indirect + github.com/ultraware/whitespace v0.2.0 // indirect + github.com/uudashr/gocognit v1.2.0 // indirect + github.com/uudashr/iface v1.3.1 // indirect + github.com/vmware-labs/yaml-jsonpath v0.3.2 // indirect + github.com/woodsbury/decimal128 v1.3.0 // indirect + github.com/xen0n/gosmopolitan v1.2.2 // indirect + github.com/yagipy/maintidx v1.0.0 // indirect + github.com/yeya24/promlinter v0.3.0 // indirect + github.com/ykadowak/zerologlint v0.1.5 // indirect + gitlab.com/bosi/decorder v0.4.2 // indirect + go-simpler.org/musttag v0.13.0 // indirect + go-simpler.org/sloglint v0.9.0 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/otel/metric v1.38.0 // indirect + go.opentelemetry.io/otel/trace v1.38.0 // indirect + go.uber.org/automaxprocs v1.6.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/crypto v0.41.0 // indirect + golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect + golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac // indirect + golang.org/x/mod v0.27.0 // indirect + golang.org/x/sync v0.16.0 // indirect + golang.org/x/sys v0.35.0 // indirect + golang.org/x/text v0.28.0 // indirect + golang.org/x/time v0.10.0 // indirect + golang.org/x/tools v0.36.0 // indirect + golang.org/x/tools/go/expect v0.1.1-deprecated // indirect + golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated // indirect + google.golang.org/protobuf v1.36.8 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + honnef.co/go/tools v0.6.1 // indirect + mvdan.cc/gofumpt v0.7.0 // indirect + mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f // indirect +) + +tool ( + github.com/gojuno/minimock/v3/cmd/minimock + github.com/golangci/golangci-lint/cmd/golangci-lint + github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen + golang.org/x/tools/cmd/stringer +) diff --git a/go.sum b/go.sum new file mode 100644 index 00000000..e5a37d14 --- /dev/null +++ b/go.sum @@ -0,0 +1,1163 @@ +4d63.com/gocheckcompilerdirectives v1.3.0 h1:Ew5y5CtcAAQeTVKUVFrE7EwHMrTO6BggtEj8BZSjZ3A= +4d63.com/gocheckcompilerdirectives v1.3.0/go.mod h1:ofsJ4zx2QAuIP/NO/NAh1ig6R1Fb18/GI7RVMwz7kAY= +4d63.com/gochecknoglobals v0.2.2 h1:H1vdnwnMaZdQW/N+NrkT1SZMTBmcwHe9Vq8lJcYYTtU= +4d63.com/gochecknoglobals v0.2.2/go.mod h1:lLxwTQjL5eIesRbvnzIP3jZtG140FnTdz+AlMa+ogt0= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/4meepo/tagalign v1.4.2 h1:0hcLHPGMjDyM1gHG58cS73aQF8J4TdVR96TZViorO9E= +github.com/4meepo/tagalign v1.4.2/go.mod h1:+p4aMyFM+ra7nb41CnFG6aSDXqRxU/w1VQqScKqDARI= +github.com/Abirdcfly/dupword v0.1.3 h1:9Pa1NuAsZvpFPi9Pqkd93I7LIYRURj+A//dFd5tgBeE= +github.com/Abirdcfly/dupword v0.1.3/go.mod h1:8VbB2t7e10KRNdwTVoxdBaxla6avbhGzb8sCTygUMhw= +github.com/Antonboom/errname v1.0.0 h1:oJOOWR07vS1kRusl6YRSlat7HFnb3mSfMl6sDMRoTBA= +github.com/Antonboom/errname v1.0.0/go.mod h1:gMOBFzK/vrTiXN9Oh+HFs+e6Ndl0eTFbtsRTSRdXyGI= +github.com/Antonboom/nilnil v1.0.1 h1:C3Tkm0KUxgfO4Duk3PM+ztPncTFlOf0b2qadmS0s4xs= +github.com/Antonboom/nilnil v1.0.1/go.mod h1:CH7pW2JsRNFgEh8B2UaPZTEPhCMuFowP/e8Udp9Nnb0= +github.com/Antonboom/testifylint v1.5.2 h1:4s3Xhuv5AvdIgbd8wOOEeo0uZG7PbDKQyKY5lGoQazk= +github.com/Antonboom/testifylint v1.5.2/go.mod h1:vxy8VJ0bc6NavlYqjZfmp6EfqXMtBgQ4+mhCojwC1P8= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs= +github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/Crocmagnon/fatcontext v0.7.1 h1:SC/VIbRRZQeQWj/TcQBS6JmrXcfA+BU4OGSVUt54PjM= +github.com/Crocmagnon/fatcontext v0.7.1/go.mod h1:1wMvv3NXEBJucFGfwOJBxSVWcoIO6emV215SMkW9MFU= +github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 h1:sHglBQTwgx+rWPdisA5ynNEsoARbiCBOyGcJM4/OzsM= +github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs= +github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.1 h1:Sz1JIXEcSfhz7fUi7xHnhpIE0thVASYjvosApmHuD2k= +github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.1/go.mod h1:n/LSCXNuIYqVfBlVXyHfMQkZDdp1/mmxfSjADd3z1Zg= +github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0= +github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/MicahParks/jwkset v0.11.0 h1:yc0zG+jCvZpWgFDFmvs8/8jqqVBG9oyIbmBtmjOhoyQ= +github.com/MicahParks/jwkset v0.11.0/go.mod h1:U2oRhRaLgDCLjtpGL2GseNKGmZtLs/3O7p+OZaL5vo0= +github.com/MicahParks/keyfunc/v3 v3.7.0 h1:pdafUNyq+p3ZlvjJX1HWFP7MA3+cLpDtg69U3kITJGM= +github.com/MicahParks/keyfunc/v3 v3.7.0/go.mod h1:z66bkCviwqfg2YUp+Jcc/xRE9IXLcMq6DrgV/+Htru0= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/OpenPeeDeeP/depguard/v2 v2.2.1 h1:vckeWVESWp6Qog7UZSARNqfu/cZqvki8zsuj3piCMx4= +github.com/OpenPeeDeeP/depguard/v2 v2.2.1/go.mod h1:q4DKzC4UcVaAvcfd41CZh0PWpGgzrVxUYBlgKNGquUo= +github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= +github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= +github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= +github.com/alecthomas/go-check-sumtype v0.3.1 h1:u9aUvbGINJxLVXiFvHUlPEaD7VDULsrxJb4Aq31NLkU= +github.com/alecthomas/go-check-sumtype v0.3.1/go.mod h1:A8TSiN3UPRw3laIgWEUOHHLPa6/r9MtoigdlP5h3K/E= +github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= +github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/alexkohler/nakedret/v2 v2.0.5 h1:fP5qLgtwbx9EJE8dGEERT02YwS8En4r9nnZ71RK+EVU= +github.com/alexkohler/nakedret/v2 v2.0.5/go.mod h1:bF5i0zF2Wo2o4X4USt9ntUWve6JbFv02Ff4vlkmS/VU= +github.com/alexkohler/prealloc v1.0.0 h1:Hbq0/3fJPQhNkN0dR95AVrr6R7tou91y0uHG5pOcUuw= +github.com/alexkohler/prealloc v1.0.0/go.mod h1:VetnK3dIgFBBKmg0YnD9F9x6Icjd+9cvfHR56wJVlKE= +github.com/alingse/asasalint v0.0.11 h1:SFwnQXJ49Kx/1GghOFz1XGqHYKp21Kq1nHad/0WQRnw= +github.com/alingse/asasalint v0.0.11/go.mod h1:nCaoMhw7a9kSJObvQyVzNTPBDbNpdocqrSP7t/cW5+I= +github.com/alingse/nilnesserr v0.1.2 h1:Yf8Iwm3z2hUUrP4muWfW83DF4nE3r1xZ26fGWUKCZlo= +github.com/alingse/nilnesserr v0.1.2/go.mod h1:1xJPrXonEtX7wyTq8Dytns5P2hNzoWymVUIaKm4HNFg= +github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ= +github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= +github.com/ashanbrown/forbidigo v1.6.0 h1:D3aewfM37Yb3pxHujIPSpTf6oQk9sc9WZi8gerOIVIY= +github.com/ashanbrown/forbidigo v1.6.0/go.mod h1:Y8j9jy9ZYAEHXdu723cUlraTqbzjKF1MUyfOKL+AjcU= +github.com/ashanbrown/makezero v1.2.0 h1:/2Lp1bypdmK9wDIq7uWBlDF1iMUpIIS4A+pF6C9IEUU= +github.com/ashanbrown/makezero v1.2.0/go.mod h1:dxlPhHbDMC6N6xICzFBSK+4njQDdK8euNO0qjQMtGY4= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bkielbasa/cyclop v1.2.3 h1:faIVMIGDIANuGPWH031CZJTi2ymOQBULs9H21HSMa5w= +github.com/bkielbasa/cyclop v1.2.3/go.mod h1:kHTwA9Q0uZqOADdupvcFJQtp/ksSnytRMe8ztxG8Fuo= +github.com/blizzy78/varnamelen v0.8.0 h1:oqSblyuQvFsW1hbBHh1zfwrKe3kcSj0rnXkKzsQ089M= +github.com/blizzy78/varnamelen v0.8.0/go.mod h1:V9TzQZ4fLJ1DSrjVDfl89H7aMnTvKkApdHeyESmyR7k= +github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= +github.com/bombsimon/wsl/v4 v4.5.0 h1:iZRsEvDdyhd2La0FVi5k6tYehpOR/R7qIUjmKk7N74A= +github.com/bombsimon/wsl/v4 v4.5.0/go.mod h1:NOQ3aLF4nD7N5YPXMruR6ZXDOAqLoM0GEpLwTdvmOSc= +github.com/breml/bidichk v0.3.2 h1:xV4flJ9V5xWTqxL+/PMFF6dtJPvZLPsyixAoPe8BGJs= +github.com/breml/bidichk v0.3.2/go.mod h1:VzFLBxuYtT23z5+iVkamXO386OB+/sVwZOpIj6zXGos= +github.com/breml/errchkjson v0.4.0 h1:gftf6uWZMtIa/Is3XJgibewBm2ksAQSY/kABDNFTAdk= +github.com/breml/errchkjson v0.4.0/go.mod h1:AuBOSTHyLSaaAFlWsRSuRBIroCh3eh7ZHh5YeelDIk8= +github.com/butuzov/ireturn v0.3.1 h1:mFgbEI6m+9W8oP/oDdfA34dLisRFCj2G6o/yiI1yZrY= +github.com/butuzov/ireturn v0.3.1/go.mod h1:ZfRp+E7eJLC0NQmk1Nrm1LOrn/gQlOykv+cVPdiXH5M= +github.com/butuzov/mirror v1.3.0 h1:HdWCXzmwlQHdVhwvsfBb2Au0r3HyINry3bDWLYXiKoc= +github.com/butuzov/mirror v1.3.0/go.mod h1:AEij0Z8YMALaq4yQj9CPPVYOyJQyiexpQEQgihajRfI= +github.com/caarlos0/env/v10 v10.0.0 h1:yIHUBZGsyqCnpTkbjk8asUlx6RFhhEs+h7TOBdgdzXA= +github.com/caarlos0/env/v10 v10.0.0/go.mod h1:ZfulV76NvVPw3tm591U4SwL3Xx9ldzBP9aGxzeN7G18= +github.com/catenacyber/perfsprint v0.8.2 h1:+o9zVmCSVa7M4MvabsWvESEhpsMkhfE7k0sHNGL95yw= +github.com/catenacyber/perfsprint v0.8.2/go.mod h1:q//VWC2fWbcdSLEY1R3l8n0zQCDPdE4IjZwyY1HMunM= +github.com/ccojocar/zxcvbn-go v1.0.2 h1:na/czXU8RrhXO4EZme6eQJLR4PzcGsahsBOAwU6I3Vg= +github.com/ccojocar/zxcvbn-go v1.0.2/go.mod h1:g1qkXtUSvHP8lhHp5GrSmTz6uWALGRMQdw6Qnz/hi60= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/charithe/durationcheck v0.0.10 h1:wgw73BiocdBDQPik+zcEoBG/ob8uyBHf2iyoHGPf5w4= +github.com/charithe/durationcheck v0.0.10/go.mod h1:bCWXb7gYRysD1CU3C+u4ceO49LoGOY1C1L6uouGNreQ= +github.com/chavacava/garif v0.1.0 h1:2JHa3hbYf5D9dsgseMKAmc/MZ109otzgNFk5s87H9Pc= +github.com/chavacava/garif v0.1.0/go.mod h1:XMyYCkEL58DF0oyW4qDjjnPWONs2HBqYKI+UIPD+Gww= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/ckaznocha/intrange v0.3.0 h1:VqnxtK32pxgkhJgYQEeOArVidIPg+ahLP7WBOXZd5ZY= +github.com/ckaznocha/intrange v0.3.0/go.mod h1:+I/o2d2A1FBHgGELbGxzIcyd3/9l9DuwjM8FsbSS3Lo= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cloudproud/graceful v1.1.1 h1:ypV1NWoG/x/CVOTyONZWObBeD61idvWblgry/v0cUIk= +github.com/cloudproud/graceful v1.1.1/go.mod h1:+WYlqLVNQYxbx6i+EkpNzrCYV3gjXcq+m3kt4ClheOg= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= +github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= +github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= +github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/curioswitch/go-reassign v0.3.0 h1:dh3kpQHuADL3cobV/sSGETA8DOv457dwl+fbBAhrQPs= +github.com/curioswitch/go-reassign v0.3.0/go.mod h1:nApPCCTtqLJN/s8HfItCcKV0jIPwluBOvZP+dsJGA88= +github.com/daixiang0/gci v0.13.5 h1:kThgmH1yBmZSBCh1EJVxQ7JsHpm5Oms0AMed/0LaH4c= +github.com/daixiang0/gci v0.13.5/go.mod h1:12etP2OniiIdP4q+kjUGrC/rUagga7ODbqsom5Eo5Yk= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/denis-tingaikin/go-header v0.5.0 h1:SRdnP5ZKvcO9KKRP1KJrhFR3RrlGuD+42t4429eC9k8= +github.com/denis-tingaikin/go-header v0.5.0/go.mod h1:mMenU5bWrok6Wl2UsZjy+1okegmwQ3UgWl4V1D8gjlY= +github.com/dhui/dktest v0.4.6 h1:+DPKyScKSEp3VLtbMDHcUq6V5Lm5zfZZVb0Sk7Ahom4= +github.com/dhui/dktest v0.4.6/go.mod h1:JHTSYDtKkvFNFHJKqCzVzqXecyv+tKt8EzceOmQOgbU= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= +github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/docker/docker v28.3.3+incompatible h1:Dypm25kh4rmk49v1eiVbsAtpAsYURjYkaKubwuBdxEI= +github.com/docker/docker v28.3.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/dprotaso/go-yit v0.0.0-20191028211022-135eb7262960/go.mod h1:9HQzr9D/0PGwMEbC3d5AB7oi67+h4TsQqItC1GVYG58= +github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 h1:PRxIJD8XjimM5aTknUK9w6DHLDox2r2M3DI4i2pnd3w= +github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936/go.mod h1:ttYvX5qlB+mlV1okblJqcSMtR4c52UKxDiX9GRBS8+Q= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/ettle/strcase v0.2.0 h1:fGNiVF21fHXpX1niBgk0aROov1LagYsOwV/xqKDKR/Q= +github.com/ettle/strcase v0.2.0/go.mod h1:DajmHElDSaX76ITe3/VHVyMin4LWSJN5Z909Wp+ED1A= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= +github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/firefart/nonamedreturns v1.0.5 h1:tM+Me2ZaXs8tfdDw3X6DOX++wMCOqzYUho6tUTYIdRA= +github.com/firefart/nonamedreturns v1.0.5/go.mod h1:gHJjDqhGM4WyPt639SOZs+G89Ko7QKH5R5BhnO6xJhw= +github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= +github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= +github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= +github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA= +github.com/getkin/kin-openapi v0.133.0 h1:pJdmNohVIJ97r4AUFtEXRXwESr8b0bD721u/Tz6k8PQ= +github.com/getkin/kin-openapi v0.133.0/go.mod h1:boAciF6cXk5FhPqe/NQeBTeenbjqU4LhWBf09ILVvWE= +github.com/ghostiam/protogetter v0.3.9 h1:j+zlLLWzqLay22Cz/aYwTHKQ88GE2DQ6GkWSYFOI4lQ= +github.com/ghostiam/protogetter v0.3.9/go.mod h1:WZ0nw9pfzsgxuRsPOFQomgDVSWtDLJRfQJEhsGbmQMA= +github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE= +github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= +github.com/go-critic/go-critic v0.12.0 h1:iLosHZuye812wnkEz1Xu3aBwn5ocCPfc9yqmFG9pa6w= +github.com/go-critic/go-critic v0.12.0/go.mod h1:DpE0P6OVc6JzVYzmM5gq5jMU31zLr4am5mB/VfFK64w= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= +github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= +github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo= +github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= +github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= +github.com/go-toolsmith/astcast v1.1.0 h1:+JN9xZV1A+Re+95pgnMgDboWNVnIMMQXwfBwLRPgSC8= +github.com/go-toolsmith/astcast v1.1.0/go.mod h1:qdcuFWeGGS2xX5bLM/c3U9lewg7+Zu4mr+xPwZIB4ZU= +github.com/go-toolsmith/astcopy v1.1.0 h1:YGwBN0WM+ekI/6SS6+52zLDEf8Yvp3n2seZITCUBt5s= +github.com/go-toolsmith/astcopy v1.1.0/go.mod h1:hXM6gan18VA1T/daUEHCFcYiW8Ai1tIwIzHY6srfEAw= +github.com/go-toolsmith/astequal v1.0.3/go.mod h1:9Ai4UglvtR+4up+bAD4+hCj7iTo4m/OXVTSLnCyTAx4= +github.com/go-toolsmith/astequal v1.1.0/go.mod h1:sedf7VIdCL22LD8qIvv7Nn9MuWJruQA/ysswh64lffQ= +github.com/go-toolsmith/astequal v1.2.0 h1:3Fs3CYZ1k9Vo4FzFhwwewC3CHISHDnVUPC4x0bI2+Cw= +github.com/go-toolsmith/astequal v1.2.0/go.mod h1:c8NZ3+kSFtFY/8lPso4v8LuJjdJiUFVnSuU3s0qrrDY= +github.com/go-toolsmith/astfmt v1.1.0 h1:iJVPDPp6/7AaeLJEruMsBUlOYCmvg0MoCfJprsOmcco= +github.com/go-toolsmith/astfmt v1.1.0/go.mod h1:OrcLlRwu0CuiIBp/8b5PYF9ktGVZUjlNMV634mhwuQ4= +github.com/go-toolsmith/astp v1.1.0 h1:dXPuCl6u2llURjdPLLDxJeZInAeZ0/eZwFJmqZMnpQA= +github.com/go-toolsmith/astp v1.1.0/go.mod h1:0T1xFGz9hicKs8Z5MfAqSUitoUYS30pDMsRVIDHs8CA= +github.com/go-toolsmith/pkgload v1.2.2 h1:0CtmHq/02QhxcF7E9N5LIFcYFsMR5rdovfqTtRKkgIk= +github.com/go-toolsmith/pkgload v1.2.2/go.mod h1:R2hxLNRKuAsiXCo2i5J6ZQPhnPMOVtU+f0arbFPWCus= +github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8= +github.com/go-toolsmith/strparse v1.1.0 h1:GAioeZUK9TGxnLS+qfdqNbA4z0SSm5zVNtCQiyP2Bvw= +github.com/go-toolsmith/strparse v1.1.0/go.mod h1:7ksGy58fsaQkGQlY8WVoBFNyEPMGuJin1rfoPS4lBSQ= +github.com/go-toolsmith/typep v1.1.0 h1:fIRYDyF+JywLfqzyhdiHzRop/GQDxxNhLGQ6gFUNHus= +github.com/go-toolsmith/typep v1.1.0/go.mod h1:fVIw+7zjdsMxDA3ITWnH1yOiw1rnTQKCsF/sk2H/qig= +github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= +github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/go-xmlfmt/xmlfmt v1.1.3 h1:t8Ey3Uy7jDSEisW2K3somuMKIpzktkWptA0iFCnRUWY= +github.com/go-xmlfmt/xmlfmt v1.1.3/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= +github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E= +github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/gojuno/minimock/v3 v3.4.7 h1:vhE5zpniyPDRT0DXd5s3DbtZJVlcbmC5k80izYtj9lY= +github.com/gojuno/minimock/v3 v3.4.7/go.mod h1:QxJk4mdPrVyYUmEZGc2yD2NONpqM/j4dWhsy9twjFHg= +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-migrate/migrate/v4 v4.19.0 h1:RcjOnCGz3Or6HQYEJ/EEVLfWnmw9KnoigPSjzhCuaSE= +github.com/golang-migrate/migrate/v4 v4.19.0/go.mod h1:9dyEcu+hO+G9hPSw8AIg50yg622pXJsoHItQnDGZkI0= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32 h1:WUvBfQL6EW/40l6OmeSBYQJNSif4O11+bmWEz+C7FYw= +github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32/go.mod h1:NUw9Zr2Sy7+HxzdjIULge71wI6yEg1lWQr7Evcu8K0E= +github.com/golangci/go-printf-func-name v0.1.0 h1:dVokQP+NMTO7jwO4bwsRwLWeudOVUPPyAKJuzv8pEJU= +github.com/golangci/go-printf-func-name v0.1.0/go.mod h1:wqhWFH5mUdJQhweRnldEywnR5021wTdZSNgwYceV14s= +github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d h1:viFft9sS/dxoYY0aiOTsLKO2aZQAPT4nlQCsimGcSGE= +github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d/go.mod h1:ivJ9QDg0XucIkmwhzCDsqcnxxlDStoTl89jDMIoNxKY= +github.com/golangci/golangci-lint v1.64.8 h1:y5TdeVidMtBGG32zgSC7ZXTFNHrsJkDnpO4ItB3Am+I= +github.com/golangci/golangci-lint v1.64.8/go.mod h1:5cEsUQBSr6zi8XI8OjmcY2Xmliqc4iYL7YoPrL+zLJ4= +github.com/golangci/misspell v0.6.0 h1:JCle2HUTNWirNlDIAUO44hUsKhOFqGPoC4LZxlaSXDs= +github.com/golangci/misspell v0.6.0/go.mod h1:keMNyY6R9isGaSAu+4Q8NMBwMPkh15Gtc8UCVoDtAWo= +github.com/golangci/plugin-module-register v0.1.1 h1:TCmesur25LnyJkpsVrupv1Cdzo+2f7zX0H6Jkw1Ol6c= +github.com/golangci/plugin-module-register v0.1.1/go.mod h1:TTpqoB6KkwOJMV8u7+NyXMrkwwESJLOkfl9TxR1DGFc= +github.com/golangci/revgrep v0.8.0 h1:EZBctwbVd0aMeRnNUsFogoyayvKHyxlV3CdUA46FX2s= +github.com/golangci/revgrep v0.8.0/go.mod h1:U4R/s9dlXZsg8uJmaR1GrloUr14D7qDl8gi2iPXJH8k= +github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed h1:IURFTjxeTfNFP0hTEi1YKjB/ub8zkpaOqFFMApi2EAs= +github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed/go.mod h1:XLXN8bNw4CGRPaqgl3bv/lhz7bsGPh4/xSaMTbo2vkQ= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= +github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +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/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gordonklaus/ineffassign v0.1.0 h1:y2Gd/9I7MdY1oEIt+n+rowjBNDcLQq3RsH5hwJd0f9s= +github.com/gordonklaus/ineffassign v0.1.0/go.mod h1:Qcp2HIAYhR7mNUVSIxZww3Guk4it82ghYcEXIAk+QT0= +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= +github.com/gostaticanalysis/analysisutil v0.7.1 h1:ZMCjoue3DtDWQ5WyU16YbjbQEQ3VuzwxALrpYd+HeKk= +github.com/gostaticanalysis/analysisutil v0.7.1/go.mod h1:v21E3hY37WKMGSnbsw2S/ojApNWb6C1//mXO48CXbVc= +github.com/gostaticanalysis/comment v1.4.1/go.mod h1:ih6ZxzTHLdadaiSnF5WY3dxUoXfXAlTaRzuaNDlSado= +github.com/gostaticanalysis/comment v1.4.2/go.mod h1:KLUTGDv6HOCotCH8h2erHKmpci2ZoR8VPu34YA2uzdM= +github.com/gostaticanalysis/comment v1.5.0 h1:X82FLl+TswsUMpMh17srGRuKaaXprTaytmEpgnKIDu8= +github.com/gostaticanalysis/comment v1.5.0/go.mod h1:V6eb3gpCv9GNVqb6amXzEUX3jXLVK/AdA+IrAMSqvEc= +github.com/gostaticanalysis/forcetypeassert v0.2.0 h1:uSnWrrUEYDr86OCxWa4/Tp2jeYDlogZiZHzGkWFefTk= +github.com/gostaticanalysis/forcetypeassert v0.2.0/go.mod h1:M5iPavzE9pPqWyeiVXSFghQjljW1+l/Uke3PXHS6ILY= +github.com/gostaticanalysis/nilerr v0.1.1 h1:ThE+hJP0fEp4zWLkWHWcRyI2Od0p7DlgYG3Uqrmrcpk= +github.com/gostaticanalysis/nilerr v0.1.1/go.mod h1:wZYb6YI5YAxxq0i1+VJbY0s2YONW0HU0GPE3+5PWN4A= +github.com/gostaticanalysis/testutil v0.3.1-0.20210208050101-bfb5c8eec0e4/go.mod h1:D+FIZ+7OahH3ePw/izIEeH5I06eKs1IKI4Xr64/Am3M= +github.com/gostaticanalysis/testutil v0.5.0 h1:Dq4wT1DdTwTGCQQv3rl3IvD5Ld0E6HiY+3Zh0sUGqw8= +github.com/gostaticanalysis/testutil v0.5.0/go.mod h1:OLQSbuM6zw2EvCcXTz1lVq5unyoNft372msDY0nY5Hs= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-immutable-radix/v2 v2.1.0 h1:CUW5RYIcysz+D3B+l1mDeXrQ7fUvGGCwJfdASSzbrfo= +github.com/hashicorp/go-immutable-radix/v2 v2.1.0/go.mod h1:hgdqLXA4f6NIjRVisM1TJ9aOJVNRqKZj+xDGF6m7PBw= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= +github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hexdigest/gowrap v1.4.3 h1:m+t8aj1pUiFQbEiE8QJg2xdYVH5DAMluLgZ9P/qEF0k= +github.com/hexdigest/gowrap v1.4.3/go.mod h1:XWL8oQW2H3fX5ll8oT3Fduh4mt2H3cUAGQHQLMUbmG4= +github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= +github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa h1:s+4MhCQ6YrzisK6hFJUX53drDT4UsSW3DEhKn0ifuHw= +github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.7.5 h1:JHGfMnQY+IEtGM63d+NGMjoRpysB2JBwDr5fsngwmJs= +github.com/jackc/pgx/v5 v5.7.5/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jgautheron/goconst v1.7.1 h1:VpdAG7Ca7yvvJk5n8dMwQhfEZJh95kl/Hl9S1OI5Jkk= +github.com/jgautheron/goconst v1.7.1/go.mod h1:aAosetZ5zaeC/2EfMeRswtxUFBpe2Hr7HzkgX4fanO4= +github.com/jingyugao/rowserrcheck v1.1.1 h1:zibz55j/MJtLsjP1OF4bSdgXxwL1b+Vn7Tjzq7gFzUs= +github.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+Dtp+nu2qOq+er9c= +github.com/jjti/go-spancheck v0.6.4 h1:Tl7gQpYf4/TMU7AT84MN83/6PutY21Nb9fuQjFTpRRc= +github.com/jjti/go-spancheck v0.6.4/go.mod h1:yAEYdKJ2lRkDA8g7X+oKUHXOWVAXSBJRv04OhF+QUjk= +github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= +github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/julz/importas v0.2.0 h1:y+MJN/UdL63QbFJHws9BVC5RpA2iq0kpjrFajTGivjQ= +github.com/julz/importas v0.2.0/go.mod h1:pThlt589EnCYtMnmhmRYY/qn9lCf/frPOK+WMx3xiJY= +github.com/karamaru-alpha/copyloopvar v1.2.1 h1:wmZaZYIjnJ0b5UoKDjUHrikcV0zuPyyxI4SVplLd2CI= +github.com/karamaru-alpha/copyloopvar v1.2.1/go.mod h1:nFmMlFNlClC2BPvNaHMdkirmTJxVCY0lhxBtlfOypMM= +github.com/kisielk/errcheck v1.9.0 h1:9xt1zI9EBfcYBvdU1nVrzMzzUPUtPKs9bVSIM3TAb3M= +github.com/kisielk/errcheck v1.9.0/go.mod h1:kQxWMMVZgIkDq7U8xtG/n2juOjbLgZtedi0D+/VL/i8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kkHAIKE/contextcheck v1.1.6 h1:7HIyRcnyzxL9Lz06NGhiKvenXq7Zw6Q0UQu/ttjfJCE= +github.com/kkHAIKE/contextcheck v1.1.6/go.mod h1:3dDbMRNBFaq8HFXWC1JyvDSPm43CmE6IuHam8Wr0rkg= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kulti/thelper v0.6.3 h1:ElhKf+AlItIu+xGnI990no4cE2+XaSu1ULymV2Yulxs= +github.com/kulti/thelper v0.6.3/go.mod h1:DsqKShOvP40epevkFrvIwkCMNYxMeTNjdWL4dqWHZ6I= +github.com/kunwardeep/paralleltest v1.0.10 h1:wrodoaKYzS2mdNVnc4/w31YaXFtsc21PCTdvWJ/lDDs= +github.com/kunwardeep/paralleltest v1.0.10/go.mod h1:2C7s65hONVqY7Q5Efj5aLzRCNLjw2h4eMc9EcypGjcY= +github.com/lasiar/canonicalheader v1.1.2 h1:vZ5uqwvDbyJCnMhmFYimgMZnJMjwljN5VGY0VKbMXb4= +github.com/lasiar/canonicalheader v1.1.2/go.mod h1:qJCeLFS0G/QlLQ506T+Fk/fWMa2VmBUiEI2cuMK4djI= +github.com/ldez/exptostd v0.4.2 h1:l5pOzHBz8mFOlbcifTxzfyYbgEmoUqjxLFHZkjlbHXs= +github.com/ldez/exptostd v0.4.2/go.mod h1:iZBRYaUmcW5jwCR3KROEZ1KivQQp6PHXbDPk9hqJKCQ= +github.com/ldez/gomoddirectives v0.6.1 h1:Z+PxGAY+217f/bSGjNZr/b2KTXcyYLgiWI6geMBN2Qc= +github.com/ldez/gomoddirectives v0.6.1/go.mod h1:cVBiu3AHR9V31em9u2kwfMKD43ayN5/XDgr+cdaFaKs= +github.com/ldez/grignotin v0.9.0 h1:MgOEmjZIVNn6p5wPaGp/0OKWyvq42KnzAt/DAb8O4Ow= +github.com/ldez/grignotin v0.9.0/go.mod h1:uaVTr0SoZ1KBii33c47O1M8Jp3OP3YDwhZCmzT9GHEk= +github.com/ldez/tagliatelle v0.7.1 h1:bTgKjjc2sQcsgPiT902+aadvMjCeMHrY7ly2XKFORIk= +github.com/ldez/tagliatelle v0.7.1/go.mod h1:3zjxUpsNB2aEZScWiZTHrAXOl1x25t3cRmzfK1mlo2I= +github.com/ldez/usetesting v0.4.2 h1:J2WwbrFGk3wx4cZwSMiCQQ00kjGR0+tuuyW0Lqm4lwA= +github.com/ldez/usetesting v0.4.2/go.mod h1:eEs46T3PpQ+9RgN9VjpY6qWdiw2/QmfiDeWmdZdrjIQ= +github.com/leonklingele/grouper v1.1.2 h1:o1ARBDLOmmasUaNDesWqWCIFH3u7hoFlM84YrjT3mIY= +github.com/leonklingele/grouper v1.1.2/go.mod h1:6D0M/HVkhs2yRKRFZUoGjeDy7EZTfFBE9gl4kjmIGkA= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/macabu/inamedparam v0.1.3 h1:2tk/phHkMlEL/1GNe/Yf6kkR/hkcUdAEY3L0hjYV1Mk= +github.com/macabu/inamedparam v0.1.3/go.mod h1:93FLICAIk/quk7eaPPQvbzihUdn/QkGDwIZEoLtpH6I= +github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= +github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= +github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/maratori/testableexamples v1.0.0 h1:dU5alXRrD8WKSjOUnmJZuzdxWOEQ57+7s93SLMxb2vI= +github.com/maratori/testableexamples v1.0.0/go.mod h1:4rhjL1n20TUTT4vdh3RDqSizKLyXp7K2u6HgraZCGzE= +github.com/maratori/testpackage v1.1.1 h1:S58XVV5AD7HADMmD0fNnziNHqKvSdDuEKdPD1rNTU04= +github.com/maratori/testpackage v1.1.1/go.mod h1:s4gRK/ym6AMrqpOa/kEbQTV4Q4jb7WeLZzVhVVVOQMc= +github.com/matoous/godox v1.1.0 h1:W5mqwbyWrwZv6OQ5Z1a/DHGMOvXYCBP3+Ht7KMoJhq4= +github.com/matoous/godox v1.1.0/go.mod h1:jgE/3fUXiTurkdHOLT5WEkThTSuE7yxHv5iWPa80afs= +github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= +github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mgechev/revive v1.7.0 h1:JyeQ4yO5K8aZhIKf5rec56u0376h8AlKNQEmjfkjKlY= +github.com/mgechev/revive v1.7.0/go.mod h1:qZnwcNhoguE58dfi96IJeSTPeZQejNeoMQLUZGi4SW4= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= +github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/moricho/tparallel v0.3.2 h1:odr8aZVFA3NZrNybggMkYO3rgPRcqjeQUlBBFVxKHTI= +github.com/moricho/tparallel v0.3.2/go.mod h1:OQ+K3b4Ln3l2TZveGCywybl68glfLEwFGqvnjok8b+U= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/nakabonne/nestif v0.3.1 h1:wm28nZjhQY5HyYPx+weN3Q65k6ilSBxDb8v5S81B81U= +github.com/nakabonne/nestif v0.3.1/go.mod h1:9EtoZochLn5iUprVDmDjqGKPofoUEBL8U4Ngq6aY7OE= +github.com/nishanths/exhaustive v0.12.0 h1:vIY9sALmw6T/yxiASewa4TQcFsVYZQQRUQJhKRf3Swg= +github.com/nishanths/exhaustive v0.12.0/go.mod h1:mEZ95wPIZW+x8kC4TgC+9YCUgiST7ecevsVDTgc2obs= +github.com/nishanths/predeclared v0.2.2 h1:V2EPdZPliZymNAn79T8RkNApBjMmVKh5XRpLm/w98Vk= +github.com/nishanths/predeclared v0.2.2/go.mod h1:RROzoN6TnGQupbC+lqggsOlcgysk3LMK/HI84Mp280c= +github.com/nunnatsa/ginkgolinter v0.19.1 h1:mjwbOlDQxZi9Cal+KfbEJTCz327OLNfwNvoZ70NJ+c4= +github.com/nunnatsa/ginkgolinter v0.19.1/go.mod h1:jkQ3naZDmxaZMXPWaS9rblH+i+GWXQCaS/JFIWcOH2s= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/oapi-codegen/nethttp-middleware v1.1.2 h1:TQwEU3WM6ifc7ObBEtiJgbRPaCe513tvJpiMJjypVPA= +github.com/oapi-codegen/nethttp-middleware v1.1.2/go.mod h1:5qzjxMSiI8HjLljiOEjvs4RdrWyMPKnExeFS2kr8om4= +github.com/oapi-codegen/oapi-codegen/v2 v2.5.1 h1:5vHNY1uuPBRBWqB2Dp0G7YB03phxLQZupZTIZaeorjc= +github.com/oapi-codegen/oapi-codegen/v2 v2.5.1/go.mod h1:ro0npU1BWkcGpCgGD9QwPp44l5OIZ94tB3eabnT7DjQ= +github.com/oapi-codegen/runtime v1.1.1 h1:EXLHh0DXIJnWhdRPN2w4MXAzFyE4CskzhNLUmtpMYro= +github.com/oapi-codegen/runtime v1.1.1/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg= +github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY= +github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw= +github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c= +github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.2/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= +github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU= +github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= +github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= +github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= +github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= +github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= +github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU= +github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w= +github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= +github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= +github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= +github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= +github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= +github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= +github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= +github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= +github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/polyfloyd/go-errorlint v1.7.1 h1:RyLVXIbosq1gBdk/pChWA8zWYLsq9UEw7a1L5TVMCnA= +github.com/polyfloyd/go-errorlint v1.7.1/go.mod h1:aXjNb1x2TNhoLsk26iv1yl7a+zTnXPhwEMtEXukiLR8= +github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= +github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk= +github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4= +github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= +github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/quasilyte/go-ruleguard v0.4.3-0.20240823090925-0fe6f58b47b1 h1:+Wl/0aFp0hpuHM3H//KMft64WQ1yX9LdJY64Qm/gFCo= +github.com/quasilyte/go-ruleguard v0.4.3-0.20240823090925-0fe6f58b47b1/go.mod h1:GJLgqsLeo4qgavUoL8JeGFNS7qcisx3awV/w9eWTmNI= +github.com/quasilyte/go-ruleguard/dsl v0.3.22 h1:wd8zkOhSNr+I+8Qeciml08ivDt1pSXe60+5DqOpCjPE= +github.com/quasilyte/go-ruleguard/dsl v0.3.22/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU= +github.com/quasilyte/gogrep v0.5.0 h1:eTKODPXbI8ffJMN+W2aE0+oL0z/nh8/5eNdiO34SOAo= +github.com/quasilyte/gogrep v0.5.0/go.mod h1:Cm9lpz9NZjEoL1tgZ2OgeUKPIxL1meE7eo60Z6Sk+Ng= +github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 h1:TCg2WBOl980XxGFEZSS6KlBGIV0diGdySzxATTWoqaU= +github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727/go.mod h1:rlzQ04UMyJXu/aOvhd8qT+hvDrFpiwqp8MRXDY9szc0= +github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 h1:M8mH9eK4OUR4lu7Gd+PU1fV2/qnDNfzT635KRSObncs= +github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567/go.mod h1:DWNGW8A4Y+GyBgPuaQJuWiy0XYftx4Xm/y5Jqk9I6VQ= +github.com/raeperd/recvcheck v0.2.0 h1:GnU+NsbiCqdC2XX5+vMZzP+jAJC5fht7rcVTAhX74UI= +github.com/raeperd/recvcheck v0.2.0/go.mod h1:n04eYkwIR0JbgD73wT8wL4JjPC3wm0nFtzBnWNocnYU= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryancurrah/gomodguard v1.3.5 h1:cShyguSwUEeC0jS7ylOiG/idnd1TpJ1LfHGpV3oJmPU= +github.com/ryancurrah/gomodguard v1.3.5/go.mod h1:MXlEPQRxgfPQa62O8wzK3Ozbkv9Rkqr+wKjSxTdsNJE= +github.com/ryanrolds/sqlclosecheck v0.5.1 h1:dibWW826u0P8jNLsLN+En7+RqWWTYrjCB9fJfSfdyCU= +github.com/ryanrolds/sqlclosecheck v0.5.1/go.mod h1:2g3dUjoS6AL4huFdv6wn55WpLIDjY7ZgUR4J8HOO/XQ= +github.com/sanposhiho/wastedassign/v2 v2.1.0 h1:crurBF7fJKIORrV85u9UUpePDYGWnwvv3+A96WvwXT0= +github.com/sanposhiho/wastedassign/v2 v2.1.0/go.mod h1:+oSmSC+9bQ+VUAxA66nBb0Z7N8CK7mscKTDYC6aIek4= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 h1:PKK9DyHxif4LZo+uQSgXNqs0jj5+xZwwfKHgph2lxBw= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.1/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= +github.com/sashamelentyev/interfacebloat v1.1.0 h1:xdRdJp0irL086OyW1H/RTZTr1h/tMEOsumirXcOJqAw= +github.com/sashamelentyev/interfacebloat v1.1.0/go.mod h1:+Y9yU5YdTkrNvoX0xHc84dxiN1iBi9+G8zZIhPVoNjQ= +github.com/sashamelentyev/usestdlibvars v1.28.0 h1:jZnudE2zKCtYlGzLVreNp5pmCdOxXUzwsMDBkR21cyQ= +github.com/sashamelentyev/usestdlibvars v1.28.0/go.mod h1:9nl0jgOfHKWNFS43Ojw0i7aRoS4j6EBye3YBhmAIRF8= +github.com/securego/gosec/v2 v2.22.2 h1:IXbuI7cJninj0nRpZSLCUlotsj8jGusohfONMrHoF6g= +github.com/securego/gosec/v2 v2.22.2/go.mod h1:UEBGA+dSKb+VqM6TdehR7lnQtIIMorYJ4/9CW1KVQBE= +github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= +github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sivchari/containedctx v1.0.3 h1:x+etemjbsh2fB5ewm5FeLNi5bUjK0V8n0RB+Wwfd0XE= +github.com/sivchari/containedctx v1.0.3/go.mod h1:c1RDvCbnJLtH4lLcYD/GqwiBSSf4F5Qk0xld2rBqzJ4= +github.com/sivchari/tenv v1.12.1 h1:+E0QzjktdnExv/wwsnnyk4oqZBUfuh89YMQT1cyuvSY= +github.com/sivchari/tenv v1.12.1/go.mod h1:1LjSOUCc25snIr5n3DtGGrENhX3LuWefcplwVGC24mw= +github.com/sonatard/noctx v0.1.0 h1:JjqOc2WN16ISWAjAk8M5ej0RfExEXtkEyExl2hLW+OM= +github.com/sonatard/noctx v0.1.0/go.mod h1:0RvBxqY8D4j9cTTTWE8ylt2vqj2EPI8fHmrxHdsaZ2c= +github.com/sourcegraph/go-diff v0.7.0 h1:9uLlrd5T46OXs5qpp8L/MTltk0zikUGi0sNNyCpA8G0= +github.com/sourcegraph/go-diff v0.7.0/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs= +github.com/speakeasy-api/jsonpath v0.6.0 h1:IhtFOV9EbXplhyRqsVhHoBmmYjblIRh5D1/g8DHMXJ8= +github.com/speakeasy-api/jsonpath v0.6.0/go.mod h1:ymb2iSkyOycmzKwbEAYPJV/yi2rSmvBCLZJcyD+VVWw= +github.com/speakeasy-api/openapi-overlay v0.10.2 h1:VOdQ03eGKeiHnpb1boZCGm7x8Haj6gST0P3SGTX95GU= +github.com/speakeasy-api/openapi-overlay v0.10.2/go.mod h1:n0iOU7AqKpNFfEt6tq7qYITC4f0yzVVdFw0S7hukemg= +github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= +github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= +github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= +github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= +github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= +github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= +github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M= +github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.12.0 h1:CZ7eSOd3kZoaYDLbXnmzgQI5RlciuXBMA+18HwHRfZQ= +github.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI= +github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= +github.com/ssgreg/nlreturn/v2 v2.2.1 h1:X4XDI7jstt3ySqGU86YGAURbxw3oTDPK9sPEi6YEwQ0= +github.com/ssgreg/nlreturn/v2 v2.2.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I= +github.com/stbenjam/no-sprintf-host-port v0.2.0 h1:i8pxvGrt1+4G0czLr/WnmyH7zbZ8Bg8etvARQ1rpyl4= +github.com/stbenjam/no-sprintf-host-port v0.2.0/go.mod h1:eL0bQ9PasS0hsyTyfTjjG+E80QIyPnBVQbYZyv20Jfk= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= +github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= +github.com/tdakkota/asciicheck v0.4.1 h1:bm0tbcmi0jezRA2b5kg4ozmMuGAFotKI3RZfrhfovg8= +github.com/tdakkota/asciicheck v0.4.1/go.mod h1:0k7M3rCfRXb0Z6bwgvkEIMleKH3kXNz9UqJ9Xuqopr8= +github.com/tenntenn/modver v1.0.1 h1:2klLppGhDgzJrScMpkj9Ujy3rXPUspSjAcev9tSEBgA= +github.com/tenntenn/modver v1.0.1/go.mod h1:bePIyQPb7UeioSRkw3Q0XeMhYZSMx9B8ePqg6SAMGH0= +github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3 h1:f+jULpRQGxTSkNYKJ51yaw6ChIqO+Je8UqsTKN/cDag= +github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY= +github.com/tetafro/godot v1.5.0 h1:aNwfVI4I3+gdxjMgYPus9eHmoBeJIbnajOyqZYStzuw= +github.com/tetafro/godot v1.5.0/go.mod h1:2oVxTBSftRTh4+MVfUaUXR6bn2GDXCaMcOG4Dk3rfio= +github.com/timakin/bodyclose v0.0.0-20241017074812-ed6a65f985e3 h1:y4mJRFlM6fUyPhoXuFg/Yu02fg/nIPFMOY8tOqppoFg= +github.com/timakin/bodyclose v0.0.0-20241017074812-ed6a65f985e3/go.mod h1:mkjARE7Yr8qU23YcGMSALbIxTQ9r9QBVahQOBRfU460= +github.com/timonwong/loggercheck v0.10.1 h1:uVZYClxQFpw55eh+PIoqM7uAOHMrhVcDoWDery9R8Lg= +github.com/timonwong/loggercheck v0.10.1/go.mod h1:HEAWU8djynujaAVX7QI65Myb8qgfcZ1uKbdpg3ZzKl8= +github.com/tomarrell/wrapcheck/v2 v2.10.0 h1:SzRCryzy4IrAH7bVGG4cK40tNUhmVmMDuJujy4XwYDg= +github.com/tomarrell/wrapcheck/v2 v2.10.0/go.mod h1:g9vNIyhb5/9TQgumxQyOEqDHsmGYcGsVMOx/xGkqdMo= +github.com/tommy-muehle/go-mnd/v2 v2.5.1 h1:NowYhSdyE/1zwK9QCLeRb6USWdoif80Ie+v+yU8u1Zw= +github.com/tommy-muehle/go-mnd/v2 v2.5.1/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw= +github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= +github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/ultraware/funlen v0.2.0 h1:gCHmCn+d2/1SemTdYMiKLAHFYxTYz7z9VIDRaTGyLkI= +github.com/ultraware/funlen v0.2.0/go.mod h1:ZE0q4TsJ8T1SQcjmkhN/w+MceuatI6pBFSxxyteHIJA= +github.com/ultraware/whitespace v0.2.0 h1:TYowo2m9Nfj1baEQBjuHzvMRbp19i+RCcRYrSWoFa+g= +github.com/ultraware/whitespace v0.2.0/go.mod h1:XcP1RLD81eV4BW8UhQlpaR+SDc2givTvyI8a586WjW8= +github.com/uudashr/gocognit v1.2.0 h1:3BU9aMr1xbhPlvJLSydKwdLN3tEUUrzPSSM8S4hDYRA= +github.com/uudashr/gocognit v1.2.0/go.mod h1:k/DdKPI6XBZO1q7HgoV2juESI2/Ofj9AcHPZhBBdrTU= +github.com/uudashr/iface v1.3.1 h1:bA51vmVx1UIhiIsQFSNq6GZ6VPTk3WNMZgRiCe9R29U= +github.com/uudashr/iface v1.3.1/go.mod h1:4QvspiRd3JLPAEXBQ9AiZpLbJlrWWgRChOKDJEuQTdg= +github.com/vmware-labs/yaml-jsonpath v0.3.2 h1:/5QKeCBGdsInyDCyVNLbXyilb61MXGi9NP674f9Hobk= +github.com/vmware-labs/yaml-jsonpath v0.3.2/go.mod h1:U6whw1z03QyqgWdgXxvVnQ90zN1BWz5V+51Ewf8k+rQ= +github.com/woodsbury/decimal128 v1.3.0 h1:8pffMNWIlC0O5vbyHWFZAt5yWvWcrHA+3ovIIjVWss0= +github.com/woodsbury/decimal128 v1.3.0/go.mod h1:C5UTmyTjW3JftjUFzOVhC20BEQa2a4ZKOB5I6Zjb+ds= +github.com/xen0n/gosmopolitan v1.2.2 h1:/p2KTnMzwRexIW8GlKawsTWOxn7UHA+jCMF/V8HHtvU= +github.com/xen0n/gosmopolitan v1.2.2/go.mod h1:7XX7Mj61uLYrj0qmeN0zi7XDon9JRAEhYQqAPLVNTeg= +github.com/yagipy/maintidx v1.0.0 h1:h5NvIsCz+nRDapQ0exNv4aJ0yXSI0420omVANTv3GJM= +github.com/yagipy/maintidx v1.0.0/go.mod h1:0qNf/I/CCZXSMhsRsrEPDZ+DkekpKLXAJfsTACwgXLk= +github.com/yeya24/promlinter v0.3.0 h1:JVDbMp08lVCP7Y6NP3qHroGAO6z2yGKQtS5JsjqtoFs= +github.com/yeya24/promlinter v0.3.0/go.mod h1:cDfJQQYv9uYciW60QT0eeHlFodotkYZlL+YcPQN+mW4= +github.com/ykadowak/zerologlint v0.1.5 h1:Gy/fMz1dFQN9JZTPjv1hxEk+sRWm05row04Yoolgdiw= +github.com/ykadowak/zerologlint v0.1.5/go.mod h1:KaUskqF3e/v59oPmdq1U1DnKcuHokl2/K1U4pmIELKg= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +gitlab.com/bosi/decorder v0.4.2 h1:qbQaV3zgwnBZ4zPMhGLW4KZe7A7NwxEhJx39R3shffo= +gitlab.com/bosi/decorder v0.4.2/go.mod h1:muuhHoaJkA9QLcYHq4Mj8FJUwDZ+EirSHRiaTcTf6T8= +go-simpler.org/assert v0.9.0 h1:PfpmcSvL7yAnWyChSjOz6Sp6m9j5lyK8Ok9pEL31YkQ= +go-simpler.org/assert v0.9.0/go.mod h1:74Eqh5eI6vCK6Y5l3PI8ZYFXG4Sa+tkr70OIPJAUr28= +go-simpler.org/musttag v0.13.0 h1:Q/YAW0AHvaoaIbsPj3bvEI5/QFP7w696IMUpnKXQfCE= +go-simpler.org/musttag v0.13.0/go.mod h1:FTzIGeK6OkKlUDVpj0iQUXZLUO1Js9+mvykDQy9C5yM= +go-simpler.org/sloglint v0.9.0 h1:/40NQtjRx9txvsB/RN022KsUJU+zaaSb/9q9BSefSrE= +go-simpler.org/sloglint v0.9.0/go.mod h1:G/OrAF6uxj48sHahCzrbarVMptL2kjWTaUeC8+fOGww= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +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/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg= +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/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/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.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= +go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= +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= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= +golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o= +golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8= +golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= +golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= +golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac h1:TSSpLIG4v+p0rPv1pNOQtl1I8knsO4S9trOxNMOLVP4= +golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= +golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= +golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211105183446-c75c47738b0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4= +golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200324003944-a576cf524670/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200329025819-fd4102a86c65/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200724022722-7017fd6b1305/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200820010801-b793a1359eac/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20201023174141-c8cfbd0f21e6/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.1-0.20210205202024-ef80cdb6ec6d/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= +golang.org/x/tools v0.1.1-0.20210302220138-2ac05c832e1a/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= +golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= +golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= +golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM= +golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= +golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM= +golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= +google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20191026110619-0b21df46bc1d/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.6.1 h1:R094WgE8K4JirYjBaOpz/AvTyUu/3wbmAoskKN/pxTI= +honnef.co/go/tools v0.6.1/go.mod h1:3puzxxljPCe8RGJX7BIy1plGbxEOZni5mR2aXe3/uk4= +mvdan.cc/gofumpt v0.7.0 h1:bg91ttqXmi9y2xawvkuMXyvAA/1ZGJqYAEGjXuP0JXU= +mvdan.cc/gofumpt v0.7.0/go.mod h1:txVFJy/Sc/mvaycET54pV8SW8gWxTlUuGHVEcncmNUo= +mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f h1:lMpcwN6GxNbWtbpI1+xzFLSW8XzX0u72NttUGVFjO3U= +mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f/go.mod h1:RSLa7mKKCNeTTMHBw5Hsy2rfJmd6O2ivt9Dw9ZqCQpQ= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/pkg/.gitignore b/pkg/.gitignore new file mode 100644 index 00000000..c3106783 --- /dev/null +++ b/pkg/.gitignore @@ -0,0 +1 @@ +!build/ \ No newline at end of file diff --git a/pkg/build/build.go b/pkg/build/build.go new file mode 100644 index 00000000..8c4c264e --- /dev/null +++ b/pkg/build/build.go @@ -0,0 +1,59 @@ +package build + +import ( + "runtime/debug" +) + +// name contains the name of the compiled binaries. This variable is +// populated while building the service through LD flags. +var name string + +// ServiceName returns the name of the service. The value is returned +// if the name is not set. +func ServiceName() string { + if name == "" { + return "" + } + + return name +} + +// version contains the version of the compiled binaries. This variable is +// populated while building the service through LD flags. +var version string + +// Version returns the version of the service. The value is returned +// if the version is not set. +func Version() string { + if version == "" { + return "" + } + + return version +} + +// commit contains the commit hash of the compiled binaries. This variable is +// set to commit hash at runtime from the build info +var commit string + +func init() { + // Read the commit hash from the build info + info, _ := debug.ReadBuildInfo() + if info != nil { + for _, setting := range info.Settings { + if setting.Key == "vcs.revision" { + commit = setting.Value + } + } + } +} + +// Commit returns the commit hash of the service. The value is +// returned if the commit hash is not set. +func Commit() string { + if commit == "" { + return "" + } + + return commit +} diff --git a/pkg/claim/jwks.go b/pkg/claim/jwks.go new file mode 100644 index 00000000..d0cccdb0 --- /dev/null +++ b/pkg/claim/jwks.go @@ -0,0 +1,24 @@ +package claim + +import ( + "github.com/MicahParks/keyfunc/v3" + "github.com/golang-jwt/jwt/v5" +) + +type JWKS struct { + keyfunc jwt.Keyfunc +} + +func (jwks *JWKS) UnmarshalText(server []byte) error { + store, err := keyfunc.NewDefault([]string{string(server)}) + if err != nil { + return err + } + + jwks.keyfunc = store.Keyfunc + return nil +} + +func (jwks JWKS) Unwrap() jwt.Keyfunc { + return jwks.keyfunc +} diff --git a/pkg/http/http.go b/pkg/http/http.go new file mode 100644 index 00000000..0d8cd9eb --- /dev/null +++ b/pkg/http/http.go @@ -0,0 +1,93 @@ +package http + +import ( + "context" + "errors" + "log/slog" + "net" + "net/http" + "os" + "time" + + "github.com/cloudproud/graceful" + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +type ResponseWriter = http.ResponseWriter + +// FileServer to serve static files eg public swagger ui +var FileServer = http.FileServer + +// FS Filesystem of static files +var FS = http.FS + +// Config holds the configuration for the http server. +type Config struct { + // ReadHeaderTimeout is the maximum duration for reading the request header. + ReadHeaderTimeout time.Duration `env:"READ_HEADER_TIMEOUT" envDefault:"5s"` + // ReadTimeout is the maximum duration for reading the entire request, including the body. + ReadTimeout time.Duration `env:"READ_TIMEOUT" envDefault:"5s"` + // IdleTimeout is the maximum amount of time to wait for the next request when keep-alives are enabled. + IdleTimeout time.Duration `env:"IDLE_TIMEOUT" envDefault:"5s"` + // WriteTimeout is the maximum duration before timing out writes of the response. + WriteTimeout time.Duration `env:"WRITE_TIMEOUT" envDefault:"10s"` + // MaxHeaderBytes controls the maximum number of bytes the server will read parsing the request header's keys and values, including the request line. + MaxHeaderBytes int `env:"MAX_HEADER_BYTES" envDefault:"512"` +} + +// NewServer creates a new http *Server with the given logger. A trace handler +// will be wrapped around the provided handler. +func NewServer(logger *zap.Logger, handler http.Handler, config Config) *Server { + stdLogger, err := zap.NewStdLogAt(logger, zapcore.ErrorLevel) + if err != nil { + logger.Error("failed to inject logger. fallback to std log", zap.Error(err)) + stdLogger = slog.NewLogLogger(slog.NewJSONHandler(os.Stderr, nil), slog.LevelError) + } + + return &Server{ + logger: logger, + Server: &http.Server{ + ReadHeaderTimeout: config.ReadHeaderTimeout, + ReadTimeout: config.ReadTimeout, + IdleTimeout: config.IdleTimeout, + WriteTimeout: config.WriteTimeout, + MaxHeaderBytes: config.MaxHeaderBytes, + ErrorLog: stdLogger, + Handler: otelhttp.NewHandler(handler, "http-server"), + }, + } +} + +// Server wraps the http.Server with a logger and custom Serve method +type Server struct { + logger *zap.Logger + *http.Server +} + +// Serve starts the http server on the given address and listens for incoming connections. +// The server will gracefully shutdown when the provided context is closed. +func (srv *Server) Serve(ctx graceful.Context, address string) { + listener, err := net.Listen("tcp", address) + if err != nil { + srv.logger.Fatal("failed to establish HTTP connection, shutting down...", zap.Error(err)) + ctx.Shutdown() + } + + go ctx.Closer(func() { + srv.logger.Info("received close signal, http server shutting down") + if err = srv.Shutdown(ctx); err != nil && !errors.Is(ctx.Err(), context.Canceled) { + srv.logger.Debug("failed to shutdown http server", zap.Error(err)) + } + }) + + srv.logger.Info("serving incoming http connections", zap.Stringer("address", listener.Addr())) + + err = srv.Server.Serve(listener) + if err != nil && !errors.Is(err, http.ErrServerClosed) { + srv.logger.Fatal("failure serving http connections, http server shutting down", zap.Error(err)) + ctx.Shutdown() + } +} diff --git a/pkg/http/json/json.go b/pkg/http/json/json.go new file mode 100644 index 00000000..2d5ae54f --- /dev/null +++ b/pkg/http/json/json.go @@ -0,0 +1,45 @@ +package json + +import ( + "encoding/json" + "io" + "net/http" + + "github.com/lunogram/platform/pkg/http/problem" +) + +const ContentType = "Content-Type" +const ApplicationJSON = "application/json" + +var Marshal = json.Marshal +var Unmarshal = json.Unmarshal + +type RawMessage = json.RawMessage + +// Decode decodes the incoming JSON request body into the given value. +func Decode(r io.ReadCloser, v any) error { + decoder := json.NewDecoder(r) + defer r.Close() + + err := decoder.Decode(v) + if err != nil { + err = problem.WithDescription(err, "invalid request body", err.Error()) + err = problem.WithStatus(err, http.StatusBadRequest) + return err + } + + return nil +} + +func Write(w http.ResponseWriter, status int, v any) { + bb, err := json.Marshal(v) + if err != nil { + // TODO: it is probably best if we would handle the error instead + panic(err) + } + + w.Header().Set(ContentType, ApplicationJSON) + + w.WriteHeader(status) + w.Write(bb) //nolint:errcheck +} diff --git a/pkg/http/problem/problem.go b/pkg/http/problem/problem.go new file mode 100644 index 00000000..fffa59fb --- /dev/null +++ b/pkg/http/problem/problem.go @@ -0,0 +1,147 @@ +package problem + +import ( + "errors" + "fmt" + "net/http" + "strings" +) + +// ErrUnimplemented is thrown whenever an unimplemented endpoint has been called. +var ErrUnimplemented = ErrorFunc(WithStatus(NewError("unimplemented endpoint", "the requested endpoint has not yet been implemented"), http.StatusNotImplemented)) + +// ErrUnauthorized is thrown whenever a user calls an unauthorized endpoint. +var ErrUnauthorized = ErrorFunc(WithStatus(NewError("unauthorized", "you are unauthorized to access the given endpoint"), http.StatusUnauthorized)) + +// ErrNotFound is thrown whenever the requested resource could not be found. +var ErrNotFound = ErrorFunc(WithStatus(NewError("resource not found", "the requested resource has not been found"), http.StatusNotFound)) + +// ErrInternal is thrown whenever an internal server error has occurred. +var ErrInternal = ErrorFunc(WithStatus(NewError("internal server error", "an internal server error has occurred"), http.StatusInternalServerError)) + +// ErrBadRequest is thrown whenever the client has sent a bad request. +var ErrBadRequest = ErrorFunc(WithStatus(NewError("bad request", "the request could not be processed"), http.StatusBadRequest)) + +// NewError creates a new error with the given title and description. +func NewError(title, description string) error { + return &withDescription{ + title: title, + description: description, + } +} + +type withDescription struct { + cause error + title string + description string +} + +func (w *withDescription) Error() string { + return fmt.Sprintf("%s\n\n%s", w.title, w.description) +} + +func (w *withDescription) Unwrap() error { return w.cause } + +// WithDescription wraps a human readable title and description around the given +// error. The title should represent a short summary of the problem. The details +// should include details on how to resolve the given error. If multiple details +// are given they will be joined using a new line character. +func WithDescription(err error, title string, description ...string) error { + if err == nil { + return nil + } + + return &withDescription{ + cause: err, + title: title, + description: strings.Join(description, "\n"), + } +} + +// AsDescription sets the error content as the error description. +func AsDescription(err error) error { + return WithDescription(err, "", err.Error()) +} + +// GetDescription attempts to unwrap the human readable description of the given +// error. The default HTTP status text for the underlaying probem status is +// returned if no description has been found. +func GetDescription(err error) (title string, description string) { + if c, ok := err.(*withDescription); ok { + return c.title, c.description + } + + if n := errors.Unwrap(err); n != nil { + return GetDescription(n) + } + + return strings.ToLower(http.StatusText(int(GetStatus(err)))), "" +} + +type withStatus struct { + cause error + status int +} + +func (w *withStatus) Error() string { return w.cause.Error() } +func (w *withStatus) Unwrap() error { return w.cause } + +// WithStatus wraps the given status around the given error. This status is +// returned to the client defining the error category. +func WithStatus(err error, status int) error { + return &withStatus{ + cause: err, + status: status, + } +} + +// GetStatus returns the underlying error status. Statuses are based on the +// registered IANA statuses. If no status has been found is the default internal +// server error status code returned. +// https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml +func GetStatus(err error) (status int) { + if c, ok := err.(*withStatus); ok { + return c.status + } + + if n := errors.Unwrap(err); n != nil { + return GetStatus(n) + } + + return http.StatusInternalServerError +} + +// Option is a functional option for configuring error behavior +type Option func(error) error + +// Describe creates an option that adds a description to an error +func Describe(description string) Option { + return func(err error) error { + var wd *withDescription + if errors.As(err, &wd) { + // If the error already has a withDescription, update it + return &withDescription{ + cause: wd.cause, + title: wd.title, + description: description, + } + } + // Otherwise, wrap it with a new withDescription + return &withDescription{ + cause: err, + title: "", + description: description, + } + } +} + +// ErrorFunc creates a function that can accept options to customize the error +func ErrorFunc(err error) func(...Option) error { + return func(opts ...Option) error { + result := err + for _, opt := range opts { + result = opt(result) + } + return result + } +} diff --git a/pkg/http/proxy.go b/pkg/http/proxy.go new file mode 100644 index 00000000..edfca3c3 --- /dev/null +++ b/pkg/http/proxy.go @@ -0,0 +1,31 @@ +package http + +import ( + "net/http" + "net/http/httputil" + "net/url" + + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/propagation" +) + +// ReverseProxy creates a reverse proxy handler that forwards requests to the specified remote URL. +// It also injects OpenTelemetry context propagation headers into the requests. +func ReverseProxy(remote *url.URL) http.Handler { + propagator := otel.GetTextMapPropagator() + + proxy := httputil.NewSingleHostReverseProxy(remote) + director := proxy.Director + proxy.Director = func(req *http.Request) { + propagator.Inject(req.Context(), propagation.HeaderCarrier(req.Header)) + director(req) + } + + return otelhttp.NewHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + r.URL.Host = remote.Host + r.URL.Scheme = remote.Scheme + r.Host = remote.Host + proxy.ServeHTTP(w, r) + }), "reverse-proxy") +} diff --git a/pkg/http/scalar/index.html b/pkg/http/scalar/index.html new file mode 100644 index 00000000..783c122f --- /dev/null +++ b/pkg/http/scalar/index.html @@ -0,0 +1,26 @@ + + + + + Lunogram API + + + + + +
+ + + + + + + + + \ No newline at end of file diff --git a/pkg/http/scalar/scalar.go b/pkg/http/scalar/scalar.go new file mode 100644 index 00000000..d304f060 --- /dev/null +++ b/pkg/http/scalar/scalar.go @@ -0,0 +1,17 @@ +package scalar + +import ( + "embed" + "fmt" + "net/http" +) + +//go:embed index.html +var FS embed.FS + +func HandleOAPI(content []byte) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/x-yaml") + fmt.Fprint(w, string(content)) + } +} diff --git a/services/console/src/views/campaign/CampaignDetails.tsx b/services/console/src/views/campaign/CampaignDetails.tsx index 9a89eeb4..0ff618c3 100644 --- a/services/console/src/views/campaign/CampaignDetails.tsx +++ b/services/console/src/views/campaign/CampaignDetails.tsx @@ -33,7 +33,7 @@ function CampaignReview({ campaign, template }: { campaign: Campaign; template: resolver: zodResolver(campaignSchema), defaultValues: { name: campaign.name || "", - provider_id: campaign.provider_id, + provider_id: campaign.provider?.id, }, }); diff --git a/services/console/src/views/campaign/template/mail/Setup.tsx b/services/console/src/views/campaign/template/mail/Setup.tsx index 7f4abda9..0de372c4 100644 --- a/services/console/src/views/campaign/template/mail/Setup.tsx +++ b/services/console/src/views/campaign/template/mail/Setup.tsx @@ -34,7 +34,7 @@ const emailSetupFormSchema = z.object({ name: z.string().optional(), email: z.email("Invalid from email address").optional(), }), - replyTo: z.email("Invalid reply-to email address").optional(), + replyTo: z.email("Invalid reply-to email address").optional().or(z.literal("")), }); const randomSubjects = [ diff --git a/services/nexus/internal/config/config.go b/services/nexus/internal/config/config.go new file mode 100644 index 00000000..96af0d02 --- /dev/null +++ b/services/nexus/internal/config/config.go @@ -0,0 +1,16 @@ +package config + +import ( + "github.com/lunogram/platform/pkg/claim" + "github.com/lunogram/platform/pkg/http" + "github.com/lunogram/platform/services/nexus/internal/store" +) + +type Service struct { + JWTSecret string `env:"JWT_SECRET"` + JWKS claim.JWKS `env:"JWKS_URL"` + Address string `env:"ADDRESS" envDefault:":8080"` + PlatformURL string `env:"PLATFORM_URL" envDefault:"http://localhost:3001"` + HTTP http.Config + Store store.Config +} diff --git a/services/nexus/internal/http/auth.go b/services/nexus/internal/http/auth.go new file mode 100644 index 00000000..ea2b04e6 --- /dev/null +++ b/services/nexus/internal/http/auth.go @@ -0,0 +1,33 @@ +package http + +import ( + "context" + + "github.com/getkin/kin-openapi/openapi3filter" + "github.com/golang-jwt/jwt/v5" + "github.com/lunogram/platform/services/nexus/internal/config" + "github.com/lunogram/platform/services/nexus/internal/http/auth" +) + +func HMAC(secret []byte) jwt.Keyfunc { + return func(token *jwt.Token) (interface{}, error) { + return secret, nil + } +} + +// Auth is a middleware function that authenticates requests by verifying the +// authorization header. It returns a AuthenticationFunc that checks the authorization +// header and adds the authenticated context to the request context. +func Auth(config config.Service) openapi3filter.AuthenticationFunc { + keyFunc := config.JWKS.Unwrap() + if config.JWTSecret != "" { + keyFunc = HMAC([]byte(config.JWTSecret)) + } + + return func(ctx context.Context, filter *openapi3filter.AuthenticationInput) error { + // TODO: support other auth strategies + token := auth.RetrieveAuthToken(filter.RequestValidationInput.Request) + _, err := jwt.Parse(token, keyFunc, jwt.WithValidMethods([]string{"RS256", "HS256"})) + return err + } +} diff --git a/services/nexus/internal/http/auth/cloud.go b/services/nexus/internal/http/auth/cloud.go new file mode 100644 index 00000000..3aeac7f8 --- /dev/null +++ b/services/nexus/internal/http/auth/cloud.go @@ -0,0 +1,55 @@ +package auth + +import ( + "encoding/json" + "net/http" + "strings" +) + +// OAuthResponse represents the OAuth token response stored in cookies +type OAuthResponse struct { + AccessToken string `json:"access_token"` + TokenType string `json:"token_type,omitempty"` + ExpiresIn int `json:"expires_in,omitempty"` + RefreshToken string `json:"refresh_token,omitempty"` + Scope string `json:"scope,omitempty"` +} + +// getCookieOAuthToken retrieves and parses the OAuth token from the 'oauth' cookie +func getCookieOAuthToken(r *http.Request) *OAuthResponse { + cookie, err := r.Cookie("oauth") + if err != nil { + return nil + } + + var oauthResp OAuthResponse + if err := json.Unmarshal([]byte(cookie.Value), &oauthResp); err != nil { + return nil + } + + return &oauthResp +} + +// RetrieveAuthToken extracts the authentication token from the request. +// It checks (in priority order): +// 1. OAuth access token from 'oauth' cookie +// 2. Session token from '__session' cookie +// 3. Authorization header (with or without Bearer prefix) +func RetrieveAuthToken(r *http.Request) string { + if oauthToken := getCookieOAuthToken(r); oauthToken != nil && oauthToken.AccessToken != "" { + return oauthToken.AccessToken + } + + if sessionCookie, err := r.Cookie("__session"); err == nil && sessionCookie.Value != "" { + return sessionCookie.Value + } + + if authHeader := r.Header.Get("Authorization"); authHeader != "" { + // Remove "Bearer " prefix if present + token := strings.TrimSpace(authHeader) + token = strings.TrimPrefix(token, "Bearer ") + return token + } + + return "" +} diff --git a/services/nexus/internal/http/controllers/v1/campaigns.go b/services/nexus/internal/http/controllers/v1/campaigns.go new file mode 100644 index 00000000..26234f2c --- /dev/null +++ b/services/nexus/internal/http/controllers/v1/campaigns.go @@ -0,0 +1,214 @@ +package v1 + +import ( + "database/sql" + "errors" + "net/http" + + "github.com/google/uuid" + "github.com/jmoiron/sqlx" + "github.com/lunogram/platform/pkg/http/json" + "github.com/lunogram/platform/pkg/http/problem" + "github.com/lunogram/platform/services/nexus/internal/store" + "github.com/lunogram/platform/services/nexus/oapi" + "go.uber.org/zap" +) + +func NewCampaignsController(logger *zap.Logger, db *sqlx.DB) *CampaignsController { + return &CampaignsController{ + logger: logger, + db: db, + store: store.NewStores(db), + } +} + +type CampaignsController struct { + logger *zap.Logger + db *sqlx.DB + store *store.Stores +} + +func (srv *CampaignsController) CreateCampaign(w http.ResponseWriter, r *http.Request, projectID uuid.UUID) { + ctx := r.Context() + body := oapi.CreateCampaignJSONRequestBody{} + err := json.Decode(r.Body, &body) + if err != nil { + oapi.WriteProblem(w, err) + return + } + + logger := srv.logger.With(zap.Stringer("project_id", projectID), zap.String("channel", string(body.Channel))) + logger.Info("creating campaign") + + project, err := srv.store.ProjectsStore.GetProject(ctx, projectID) + if errors.Is(err, sql.ErrNoRows) { + logger.Error("project not found", zap.Stringer("project_id", projectID)) + oapi.WriteProblem(w, problem.ErrNotFound(problem.Describe("project not found"))) + return + } + + if err != nil { + logger.Error("failed to get project", zap.Error(err)) + oapi.WriteProblem(w, err) + return + } + + if body.ProviderId == nil { + provider, err := srv.store.ProvidersStore.GetDefaultProviderChannel(ctx, project.ID, string(body.Channel)) + if err != nil && !errors.Is(err, sql.ErrNoRows) { + logger.Error("failed to get default provider", zap.Error(err)) + oapi.WriteProblem(w, err) + return + } + + // NOTE: if no default provider is found (ErrNoRows), we proceed with nil ProviderId. + // This allows campaign creation to continue even if no provider is set for the channel. + // Downstream, a nil ProviderId means the campaign will not be associated with any provider, + // and it is up to later validation or business logic to handle this case (e.g., by rejecting + // campaigns without a provider, or allowing them for manual assignment). + if err == nil { + body.ProviderId = &provider.ID + } + } + + tx, err := srv.db.BeginTxx(ctx, nil) + if err != nil { + logger.Error("unexpected error while attempting to start a transaction", zap.Error(err)) + oapi.WriteProblem(w, err) + return + } + + defer tx.Rollback() //nolint:errcheck + + campaigns := store.NewCampaignsStore(tx) + templates := store.NewTemplatesStore(tx) + + campaignID, err := campaigns.CreateCampaign(ctx, store.Campaign{ + ProjectID: project.ID, + Name: body.Name, + Channel: string(body.Channel), + ProviderID: body.ProviderId, + SubscriptionID: body.SubscriptionId, + }) + if err != nil { + logger.Error("failed to create campaign", zap.Error(err)) + oapi.WriteProblem(w, err) + return + } + + // TODO: create audit log + + _, err = templates.CreateTemplate(ctx, project.ID, campaignID, string(body.Channel), project.Locale) + if err != nil { + logger.Error("failed to create template", zap.Error(err)) + oapi.WriteProblem(w, err) + return + } + + err = tx.Commit() + if err != nil { + logger.Error("unexpected error while attempting to commit transaction", zap.Error(err)) + oapi.WriteProblem(w, err) + return + } + + logger.Info("campaign created") + campaign, err := srv.store.GetCampaign(ctx, projectID, campaignID) + if err != nil { + logger.Error("failed to fetch created campaign", zap.Error(err)) + oapi.WriteProblem(w, err) + return + } + + json.Write(w, http.StatusCreated, campaign.OAPI()) +} + +func (srv *CampaignsController) ListCampaigns(w http.ResponseWriter, r *http.Request, projectID uuid.UUID, params oapi.ListCampaignsParams) { + ctx := r.Context() + logger := srv.logger.With(zap.Stringer("project_id", projectID)) + logger.Info("listing campaigns") + + pagination := store.Pagination{ + Limit: params.Limit.ToInt(), + Offset: params.Offset.ToInt(), + } + + result, total, err := srv.store.ListCampaigns(ctx, projectID, pagination) + if err != nil { + logger.Error("failed to list campaigns", zap.Error(err)) + oapi.WriteProblem(w, err) + return + } + + logger.Info("listed campaigns", zap.Int("count", len(result))) + json.Write(w, http.StatusOK, oapi.CampaignListResponse{ + Total: total, + Limit: pagination.Limit, + Offset: pagination.Offset, + Results: result.OAPI(), + }) +} + +func (srv *CampaignsController) GetCampaign(w http.ResponseWriter, r *http.Request, projectID uuid.UUID, campaignID uuid.UUID) { + ctx := r.Context() + logger := srv.logger.With(zap.Stringer("project_id", projectID), zap.Stringer("campaign_id", campaignID)) + logger.Info("getting campaign") + + campaign, err := srv.store.GetCampaign(ctx, projectID, campaignID) + if err != nil { + logger.Error("failed to fetch campaign", zap.Error(err)) + oapi.WriteProblem(w, err) + return + } + + logger.Info("campaign retrieved") + json.Write(w, http.StatusOK, campaign.OAPI()) +} + +func (srv *CampaignsController) UpdateCampaign(w http.ResponseWriter, r *http.Request, projectID uuid.UUID, campaignID uuid.UUID) { + logger := srv.logger.With(zap.Stringer("project_id", projectID), zap.Stringer("campaign_id", campaignID)) + logger.Info("updating campaign") + + ctx := r.Context() + body := oapi.UpdateCampaignJSONRequestBody{} + err := json.Decode(r.Body, &body) + if err != nil { + oapi.WriteProblem(w, err) + return + } + + _, err = srv.store.GetCampaign(ctx, projectID, campaignID) + if errors.Is(err, sql.ErrNoRows) { + logger.Error("campaign not found", zap.Stringer("campaign_id", campaignID)) + oapi.WriteProblem(w, problem.ErrNotFound(problem.Describe("campaign not found"))) + return + } + + if err != nil { + logger.Error("failed to get campaign", zap.Error(err)) + oapi.WriteProblem(w, err) + return + } + + updated := store.CampaignUpdate{ + Name: body.Name, + ProviderID: body.ProviderId, + } + + err = srv.store.UpdateCampaign(ctx, projectID, campaignID, updated) + if err != nil { + logger.Error("failed to update campaign", zap.Error(err)) + oapi.WriteProblem(w, err) + return + } + + campaign, err := srv.store.GetCampaign(ctx, projectID, campaignID) + if err != nil { + logger.Error("failed to fetch updated campaign", zap.Error(err)) + oapi.WriteProblem(w, err) + return + } + + logger.Info("campaign updated") + json.Write(w, http.StatusOK, campaign.OAPI()) +} diff --git a/services/nexus/internal/http/controllers/v1/controller.go b/services/nexus/internal/http/controllers/v1/controller.go new file mode 100644 index 00000000..349589f3 --- /dev/null +++ b/services/nexus/internal/http/controllers/v1/controller.go @@ -0,0 +1,18 @@ +package v1 + +import ( + "github.com/jmoiron/sqlx" + "go.uber.org/zap" +) + +func NewController(logger *zap.Logger, db *sqlx.DB) *Controller { + return &Controller{ + CampaignsController: NewCampaignsController(logger, db), + TemplatesController: NewTemplatesController(logger, db), + } +} + +type Controller struct { + *CampaignsController + *TemplatesController +} diff --git a/services/nexus/internal/http/controllers/v1/http.go b/services/nexus/internal/http/controllers/v1/http.go new file mode 100644 index 00000000..60f8d9c0 --- /dev/null +++ b/services/nexus/internal/http/controllers/v1/http.go @@ -0,0 +1,17 @@ +package v1 + +import ( + _ "embed" + "net/http" + + "github.com/go-chi/chi/v5" + "github.com/lunogram/platform/services/nexus/oapi" +) + +// Use set's up the v1 OpenAPI endpoints server handler within the given router. +func Use(router chi.Router, server oapi.ServerInterface, middleware ...oapi.MiddlewareFunc) http.Handler { + return oapi.HandlerWithOptions(server, oapi.ChiServerOptions{ + BaseRouter: router, + Middlewares: middleware, + }) +} diff --git a/services/nexus/internal/http/controllers/v1/templates.go b/services/nexus/internal/http/controllers/v1/templates.go new file mode 100644 index 00000000..6c47d6b9 --- /dev/null +++ b/services/nexus/internal/http/controllers/v1/templates.go @@ -0,0 +1,41 @@ +package v1 + +import ( + "net/http" + + "github.com/google/uuid" + "github.com/jmoiron/sqlx" + "github.com/lunogram/platform/pkg/http/json" + "github.com/lunogram/platform/services/nexus/internal/store" + "github.com/lunogram/platform/services/nexus/oapi" + "go.uber.org/zap" +) + +func NewTemplatesController(logger *zap.Logger, db *sqlx.DB) *TemplatesController { + return &TemplatesController{ + logger: logger, + db: db, + store: store.NewStores(db), + } +} + +type TemplatesController struct { + logger *zap.Logger + db *sqlx.DB + store *store.Stores +} + +func (srv *TemplatesController) GetTemplate(w http.ResponseWriter, r *http.Request, projectID uuid.UUID, templateID uuid.UUID) { + ctx := r.Context() + logger := srv.logger.With(zap.Stringer("project_id", projectID), zap.Stringer("template_id", templateID)) + logger.Info("getting template") + + template, err := srv.store.TemplatesStore.GetTemplate(ctx, projectID, templateID) + if err != nil { + logger.Error("failed to get template", zap.Error(err)) + oapi.WriteProblem(w, err) + return + } + + json.Write(w, http.StatusOK, template.OAPI()) +} diff --git a/services/nexus/internal/http/http.go b/services/nexus/internal/http/http.go new file mode 100644 index 00000000..d73703f5 --- /dev/null +++ b/services/nexus/internal/http/http.go @@ -0,0 +1,46 @@ +package http + +import ( + _ "embed" + "fmt" + "net/url" + + "github.com/cloudproud/graceful" + "github.com/getkin/kin-openapi/openapi3filter" + "github.com/go-chi/chi/v5" + "github.com/jmoiron/sqlx" + "github.com/lunogram/platform/pkg/http" + "github.com/lunogram/platform/services/nexus/internal/config" + v1 "github.com/lunogram/platform/services/nexus/internal/http/controllers/v1" + "github.com/lunogram/platform/services/nexus/oapi" + "go.uber.org/zap" +) + +// NewServer constructs a new HTTP server and it's routes. The returned server +// could be used to listen and serve incoming requests on the given address. +func NewServer(ctx graceful.Context, logger *zap.Logger, config config.Service, db *sqlx.DB) (*http.Server, error) { + spec, err := oapi.Spec() + if err != nil { + return nil, fmt.Errorf("failed to load OpenAPI spec: %w", err) + } + + options := openapi3filter.Options{ + AuthenticationFunc: Auth(config), + } + + router := chi.NewRouter() + router.Use(Logger(logger)) + + router.Use(oapi.Scalar()) + v1.Use(router, v1.NewController(logger, db), oapi.Validator(spec, options)) + + platform, err := url.Parse(config.PlatformURL) + if err != nil { + return nil, fmt.Errorf("invalid platform URL: %w", err) + } + + // NOTE: during the migration we proxy all unknown requests to the platform service. + router.Handle("/*", http.ReverseProxy(platform)) + + return http.NewServer(logger, router, config.HTTP), nil +} diff --git a/services/nexus/internal/http/logger.go b/services/nexus/internal/http/logger.go new file mode 100644 index 00000000..0db708ae --- /dev/null +++ b/services/nexus/internal/http/logger.go @@ -0,0 +1,21 @@ +package http + +import ( + "net/http" + + "go.uber.org/zap" +) + +func Logger(l *zap.Logger) func(next http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + fn := func(w http.ResponseWriter, r *http.Request) { + l.Debug("incoming request", + zap.String("proto", r.Proto), + zap.String("method", r.Method), + zap.String("path", r.URL.Path)) + + next.ServeHTTP(w, r) + } + return http.HandlerFunc(fn) + } +} diff --git a/services/nexus/internal/store/campaigns.go b/services/nexus/internal/store/campaigns.go new file mode 100644 index 00000000..61ce5812 --- /dev/null +++ b/services/nexus/internal/store/campaigns.go @@ -0,0 +1,174 @@ +package store + +import ( + "context" + "time" + + "github.com/google/uuid" + "github.com/lunogram/platform/services/nexus/oapi" +) + +type Campaigns []Campaign + +func (campaigns Campaigns) OAPI() []oapi.Campaign { + result := make([]oapi.Campaign, len(campaigns)) + for index, campaign := range campaigns { + result[index] = campaign.OAPI() + } + return result +} + +type Campaign struct { + ID uuid.UUID `db:"id"` + ProjectID uuid.UUID `db:"project_id"` + Name string `db:"name"` + Channel string `db:"channel"` + ProviderID *uuid.UUID `db:"provider_id"` + SubscriptionID *uuid.UUID `db:"subscription_id"` + Delivery JSONB[Delivery] `db:"delivery"` + Templates Templates `db:"-"` + Provider *Provider `db:"-"` + CreatedAt time.Time `db:"created_at"` + UpdatedAt time.Time `db:"updated_at"` + DeletedAt *time.Time `db:"deleted_at"` +} + +func (campaign Campaign) OAPI() oapi.Campaign { + return oapi.Campaign{ + Id: campaign.ID, + ProjectId: campaign.ProjectID, + Name: campaign.Name, + Channel: oapi.CampaignChannel(campaign.Channel), + SubscriptionId: campaign.SubscriptionID, + Delivery: campaign.Delivery.Data.OAPI(), + Templates: campaign.Templates.OAPI(), + Provider: campaign.Provider.OAPI(), + CreatedAt: campaign.CreatedAt, + UpdatedAt: campaign.UpdatedAt, + } +} + +type Delivery struct { + Sent int `db:"sent"` + Opens int `db:"opens"` + Total int `db:"total"` + Clicks int `db:"clicks"` +} + +func (delivery Delivery) OAPI() oapi.Delivery { + return oapi.Delivery{ + Sent: delivery.Sent, + Opens: delivery.Opens, + Total: delivery.Total, + Clicks: delivery.Clicks, + } +} + +func NewCampaignsStore(db DB) *CampaignsStore { + return &CampaignsStore{ + db: db, + templates: NewTemplatesStore(db), + providers: NewProvidersStore(db), + } +} + +type CampaignsStore struct { + db DB + templates *TemplatesStore + providers *ProvidersStore +} + +func (s *CampaignsStore) CreateCampaign(ctx context.Context, campaign Campaign) (uuid.UUID, error) { + stmt := ` + INSERT INTO campaigns (project_id, name, channel, provider_id, subscription_id) + VALUES ($1, $2, $3, $4, $5) + RETURNING id` + + var id uuid.UUID + err := s.db.GetContext(ctx, &id, stmt, campaign.ProjectID, campaign.Name, campaign.Channel, campaign.ProviderID, campaign.SubscriptionID) + if err != nil { + return uuid.Nil, err + } + + return id, nil +} + +func (s *CampaignsStore) ListCampaigns(ctx context.Context, project uuid.UUID, pagination Pagination) (Campaigns, int, error) { + query := ` + SELECT id, project_id, name, channel, provider_id, subscription_id, delivery, created_at, updated_at, deleted_at, + COUNT(*) OVER () AS total_count + FROM campaigns + WHERE project_id = $1 + AND deleted_at IS NULL + ORDER BY created_at DESC + LIMIT $2 OFFSET $3` + + var results []struct { + Campaign + TotalCount int `db:"total_count"` + } + err := s.db.SelectContext(ctx, &results, query, project, pagination.Limit, pagination.Offset) + if err != nil { + return nil, 0, err + } + + campaigns := make(Campaigns, len(results)) + total := 0 + + for i, r := range results { + campaigns[i] = r.Campaign + if i == 0 { + total = r.TotalCount + } + } + + return campaigns, total, nil +} + +func (s *CampaignsStore) GetCampaign(ctx context.Context, projectID, campaignID uuid.UUID) (*Campaign, error) { + query := ` + SELECT id, project_id, name, channel, provider_id, subscription_id, delivery, created_at, updated_at, deleted_at + FROM campaigns + WHERE project_id = $1 + AND id = $2 + AND deleted_at IS NULL` + + var campaign Campaign + err := s.db.GetContext(ctx, &campaign, query, projectID, campaignID) + if err != nil { + return nil, err + } + + campaign.Templates, err = s.templates.ListTemplates(ctx, projectID, campaignID) + if err != nil { + return nil, err + } + + if campaign.ProviderID != nil { + campaign.Provider, err = s.providers.GetProvider(ctx, *campaign.ProviderID) + if err != nil { + return nil, err + } + } + + return &campaign, nil +} + +type CampaignUpdate struct { + Name *string + ProviderID *uuid.UUID +} + +func (s *CampaignsStore) UpdateCampaign(ctx context.Context, projectID, campaignID uuid.UUID, update CampaignUpdate) error { + query := ` + UPDATE campaigns + SET + name = COALESCE($1, name), + provider_id = COALESCE($2, provider_id) + WHERE project_id = $3 + AND id = $4 + AND deleted_at IS NULL` + + _, err := s.db.ExecContext(ctx, query, update.Name, update.ProviderID, projectID, campaignID) + return err +} diff --git a/services/nexus/internal/store/migrate.go b/services/nexus/internal/store/migrate.go new file mode 100644 index 00000000..d50015dc --- /dev/null +++ b/services/nexus/internal/store/migrate.go @@ -0,0 +1,47 @@ +package store + +import ( + "database/sql" + "embed" + "fmt" + + "github.com/golang-migrate/migrate/v4" + "github.com/golang-migrate/migrate/v4/database/pgx/v5" + "github.com/golang-migrate/migrate/v4/source/iofs" +) + +//go:embed migrations/*.sql +var migrations embed.FS + +// Migrate ensures all migrations defined in the embedded file system are +// applied to the database defined in uri. An error is returned if any of the migrations fail. +func Migrate(config Config) error { + fs, err := iofs.New(migrations, "migrations") + if err != nil { + return fmt.Errorf("failed to load embedded migration: %w", err) + } + + conn, err := sql.Open("pgx", config.URI) + if err != nil { + return fmt.Errorf("failed to open database connection: %w", err) + } + defer conn.Close() + + db, err := pgx.WithInstance(conn, &pgx.Config{}) + if err != nil { + return fmt.Errorf("failed to create migration database instance: %w", err) + } + defer db.Close() //nolint:errcheck + + migrator, err := migrate.NewWithInstance("iofs", fs, "pgx", db) + if err != nil { + return fmt.Errorf("failed to construct migrator: %w", err) + } + + err = migrator.Up() + if err != nil && err != migrate.ErrNoChange { + return fmt.Errorf("failed to run migration: %w", err) + } + + return nil +} diff --git a/services/nexus/internal/store/migrations/1764106028_migration.down.sql b/services/nexus/internal/store/migrations/1764106028_migration.down.sql new file mode 100644 index 00000000..e69de29b diff --git a/services/nexus/internal/store/migrations/1764106028_migration.up.sql b/services/nexus/internal/store/migrations/1764106028_migration.up.sql new file mode 100644 index 00000000..9ddebb88 --- /dev/null +++ b/services/nexus/internal/store/migrations/1764106028_migration.up.sql @@ -0,0 +1,752 @@ +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; + +CREATE OR REPLACE function set_updated_at() RETURNS trigger + LANGUAGE plpgsql + AS $$ +BEGIN + new.updated_at := current_timestamp; + return new; +END; +$$; + +-- Table Definition +CREATE TABLE "access_tokens" ( + "id" uuid NOT NULL DEFAULT uuid_generate_v4(), + "admin_id" uuid NOT NULL, + "token" text NOT NULL, + "revoked" bool DEFAULT false, + "expires_at" timestamptz, + "created_at" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, + "ip" varchar(255), + "user_agent" varchar(255), + PRIMARY KEY ("id") +); + +-- Table Definition +CREATE TABLE "audits" ( + "id" uuid NOT NULL DEFAULT uuid_generate_v4(), + "project_id" uuid NOT NULL, + "admin_id" uuid, + "item_type" varchar(50) NOT NULL, + "item_id" uuid NOT NULL, + "event" varchar(50) NOT NULL, + "object" jsonb, + "object_changes" jsonb, + "created_at" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY ("id") +); + +-- Table Definition +CREATE TABLE "job_locks" ( + "id" uuid NOT NULL DEFAULT uuid_generate_v4(), + "key" varchar(255), + "owner" varchar(255), + "expiration" timestamptz, + "created_at" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY ("id") +); + +-- Table Definition +CREATE TABLE "campaigns" ( + "id" uuid NOT NULL DEFAULT uuid_generate_v4(), + "type" varchar(255), + "project_id" uuid NOT NULL, + "list_ids" jsonb, + "exclusion_list_ids" jsonb, + "name" varchar(255) DEFAULT ''::character varying, + "channel" varchar(255) NOT NULL, + "provider_id" uuid, + "subscription_id" uuid, + "state" varchar(20), + "delivery" jsonb NOT NULL DEFAULT '{}'::jsonb, + "send_at" timestamptz, + "send_in_user_timezone" bool DEFAULT false, + "created_at" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, + "deleted_at" timestamptz, + PRIMARY KEY ("id") +); + +-- Table Definition +CREATE TABLE "campaign_sends" ( + "id" uuid DEFAULT uuid_generate_v4(), + "campaign_id" uuid NOT NULL, + "user_id" uuid NOT NULL, + "state" varchar(50), + "send_at" timestamptz, + "opened_at" timestamptz, + "clicks" int4 DEFAULT 0, + "created_at" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, + "reference_type" varchar(255), + "reference_id" varchar(255) NOT NULL DEFAULT '0'::character varying, + PRIMARY KEY ("campaign_id","user_id","reference_id") +); + +-- Table Definition +CREATE TABLE "devices" ( + "id" uuid NOT NULL DEFAULT uuid_generate_v4(), + "project_id" uuid NOT NULL, + "user_id" uuid NOT NULL, + "device_id" text NOT NULL, + "token" varchar(255), + "os" varchar(255), + "os_version" varchar(255), + "model" varchar(255), + "app_version" varchar(255), + "app_build" varchar(255), + "created_at" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY ("id") +); + +-- Table Definition +CREATE TABLE "entity_tags" ( + "id" uuid NOT NULL DEFAULT uuid_generate_v4(), + "tag_id" uuid NOT NULL, + "entity" varchar(255), + "entity_id" uuid NOT NULL, + "created_at" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY ("id") +); + +-- Table Definition +CREATE TABLE "images" ( + "id" uuid NOT NULL DEFAULT uuid_generate_v4(), + "project_id" uuid NOT NULL, + "name" varchar(255) DEFAULT ''::character varying, + "original_name" varchar(255), + "extension" varchar(255), + "alt" varchar(255), + "file_size" int4, + "created_at" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY ("id") +); + +-- Table Definition +CREATE TABLE "journey_steps" ( + "id" uuid NOT NULL DEFAULT uuid_generate_v4(), + "type" varchar(255) DEFAULT ''::character varying, + "journey_id" uuid, + "child_id" uuid, + "data" jsonb, + "x" float8 NOT NULL DEFAULT 0, + "y" float8 NOT NULL DEFAULT 0, + "created_at" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, + "external_id" varchar(128), + "data_key" varchar(255), + "stats" jsonb, + "stats_at" timestamptz, + "name" varchar(128), + "next_scheduled_at" timestamptz, + PRIMARY KEY ("id") +); + +-- Table Definition +CREATE TABLE "journey_step_child" ( + "id" uuid NOT NULL DEFAULT uuid_generate_v4(), + "step_id" uuid NOT NULL, + "child_id" uuid NOT NULL, + "data" jsonb, + "created_at" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, + "priority" int4 NOT NULL DEFAULT 0, + "path" varchar(128), + PRIMARY KEY ("id") +); + +-- Table Definition +CREATE TABLE "journey_user_step" ( + "id" uuid NOT NULL DEFAULT uuid_generate_v4(), + "user_id" uuid, + "journey_id" uuid, + "step_id" uuid, + "type" varchar(255), + "created_at" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, + "delay_until" timestamptz, + "entrance_id" uuid, + "ended_at" timestamptz, + "data" jsonb, + "ref" varchar(64), + PRIMARY KEY ("id") +); + +-- Table Definition +CREATE TABLE "journeys" ( + "id" uuid NOT NULL DEFAULT uuid_generate_v4(), + "project_id" uuid NOT NULL, + "name" varchar(255) DEFAULT ''::character varying, + "description" varchar(2048) DEFAULT ''::character varying, + "created_at" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, + "deleted_at" timestamptz, + "stats" jsonb, + "stats_at" timestamptz, + "parent_id" uuid, + "status" varchar(255), + PRIMARY KEY ("id") +); + +-- Table Definition +CREATE TABLE "lists" ( + "id" uuid NOT NULL DEFAULT uuid_generate_v4(), + "project_id" uuid NOT NULL, + "name" varchar(255) DEFAULT ''::character varying, + "type" varchar(25), + "state" varchar(25), + "rule" jsonb, + "rule_id" uuid, + "users_count" int4, + "version" int4 NOT NULL DEFAULT 0, + "created_at" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, + "deleted_at" timestamptz, + "is_visible" bool DEFAULT true, + "refreshed_at" timestamptz, + PRIMARY KEY ("id") +); + +-- Table Definition +CREATE TABLE "locales" ( + "id" uuid NOT NULL DEFAULT uuid_generate_v4(), + "project_id" uuid NOT NULL, + "key" varchar(255), + "label" varchar(255), + "created_at" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY ("id") +); + +-- Table Definition +CREATE TABLE "notifications" ( + "id" uuid NOT NULL DEFAULT uuid_generate_v4(), + "project_id" uuid NOT NULL, + "user_id" uuid NOT NULL, + "content_type" varchar(255), + "content" jsonb, + "read_at" timestamptz, + "expires_at" timestamptz, + "created_at" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY ("id") +); + +-- Table Definition +CREATE TABLE "project_admins" ( + "id" uuid NOT NULL DEFAULT uuid_generate_v4(), + "project_id" uuid NOT NULL, + "admin_id" uuid NOT NULL, + "created_at" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, + "deleted_at" timestamptz, + "role" varchar(64) NOT NULL DEFAULT 'support'::character varying, + PRIMARY KEY ("id") +); + +-- Table Definition +CREATE TABLE "project_api_keys" ( + "id" uuid NOT NULL DEFAULT uuid_generate_v4(), + "project_id" uuid NOT NULL, + "value" varchar(255) NOT NULL, + "scope" varchar(20), + "name" varchar(255) NOT NULL, + "description" varchar(2048), + "created_at" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, + "deleted_at" timestamptz, + "role" varchar(64) NOT NULL DEFAULT 'support'::character varying, + PRIMARY KEY ("id") +); + +-- Table Definition +CREATE TABLE "users" ( + "id" uuid NOT NULL DEFAULT uuid_generate_v4(), + "project_id" uuid NOT NULL, + "anonymous_id" varchar(255), + "external_id" varchar(255), + "email" varchar(255), + "phone" varchar(64), + "data" jsonb, + "devices" jsonb, + "timezone" varchar(50), + "locale" varchar(255), + "created_at" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, + "unsubscribe_ids" _uuid NOT NULL DEFAULT '{}'::uuid[], + "version" int4 NOT NULL DEFAULT 0, + "has_push_device" bool DEFAULT false, + PRIMARY KEY ("id") +); + +-- Table Definition +CREATE TABLE "subscriptions" ( + "id" uuid NOT NULL DEFAULT uuid_generate_v4(), + "project_id" uuid NOT NULL, + "name" varchar(255) DEFAULT ''::character varying, + "channel" varchar(255) NOT NULL, + "created_at" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, + "is_public" bool NOT NULL DEFAULT true, + PRIMARY KEY ("id") +); + +-- Table Definition +CREATE TABLE "tags" ( + "id" uuid NOT NULL DEFAULT uuid_generate_v4(), + "project_id" uuid NOT NULL, + "name" varchar(255) DEFAULT ''::character varying, + "created_at" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY ("id") +); + +-- Table Definition +CREATE TABLE "rules" ( + "id" uuid NOT NULL DEFAULT uuid_generate_v4(), + "project_id" uuid NOT NULL, + "root_id" uuid, + "parent_id" uuid, + "type" varchar(255), + "group" varchar(255), + "path" varchar(255), + "operator" varchar(255), + "value" varchar(255), + "created_at" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY ("id") +); + +DROP TYPE IF EXISTS "project_rule_paths_visibility"; +CREATE TYPE "project_rule_paths_visibility" AS ENUM ('public', 'hidden', 'classified'); + +-- Table Definition +CREATE TABLE "project_rule_paths" ( + "id" uuid NOT NULL DEFAULT uuid_generate_v4(), + "project_id" uuid NOT NULL, + "path" varchar(255) NOT NULL, + "name" varchar(255), + "type" varchar(50) NOT NULL, + "created_at" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, + "data_type" varchar(255) DEFAULT 'string'::character varying, + "visibility" "project_rule_paths_visibility" NOT NULL DEFAULT 'public'::project_rule_paths_visibility, + PRIMARY KEY ("id") +); + +-- Table Definition +CREATE TABLE "resources" ( + "id" uuid NOT NULL DEFAULT uuid_generate_v4(), + "project_id" uuid NOT NULL, + "type" varchar(255), + "name" varchar(255), + "value" jsonb, + "created_at" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY ("id") +); + +-- Table Definition +CREATE TABLE "rule_evaluations" ( + "id" uuid NOT NULL DEFAULT uuid_generate_v4(), + "rule_id" uuid NOT NULL, + "user_id" uuid NOT NULL, + "result" bool, + "created_at" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY ("id") +); + +-- Table Definition +CREATE TABLE "templates" ( + "id" uuid NOT NULL DEFAULT uuid_generate_v4(), + "project_id" uuid NOT NULL, + "type" varchar(50), + "data" jsonb NOT NULL DEFAULT '{}'::jsonb, + "created_at" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, + "locale" varchar(50), + "campaign_id" uuid NOT NULL, + PRIMARY KEY ("id") +); + +-- Table Definition +CREATE TABLE "user_events" ( + "id" uuid NOT NULL DEFAULT uuid_generate_v4(), + "name" varchar(255) DEFAULT ''::character varying, + "project_id" uuid NOT NULL, + "user_id" uuid NOT NULL, + "data" jsonb, + "created_at" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY ("id") +); + +-- Table Definition +CREATE TABLE "user_list" ( + "id" uuid NOT NULL DEFAULT uuid_generate_v4(), + "user_id" uuid NOT NULL, + "list_id" uuid NOT NULL, + "event_id" uuid, + "version" int4 NOT NULL DEFAULT 0, + "created_at" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, + "deleted_at" timestamptz, + PRIMARY KEY ("id") +); + +-- Table Definition +CREATE TABLE "user_subscription" ( + "id" uuid NOT NULL DEFAULT uuid_generate_v4(), + "subscription_id" uuid NOT NULL, + "user_id" uuid NOT NULL, + "state" int2, + "created_at" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY ("id") +); + +-- Table Definition +CREATE TABLE "organizations" ( + "id" uuid NOT NULL DEFAULT uuid_generate_v4(), + "auth" jsonb, + "created_at" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, + "tracking_deeplink_mirror_url" varchar(255), + "notification_provider_id" uuid, + "name" varchar(255) NOT NULL DEFAULT ''::character varying, + PRIMARY KEY ("id") +); + +-- Table Definition +CREATE TABLE "admins" ( + "id" uuid NOT NULL DEFAULT uuid_generate_v4(), + "organization_id" uuid, + "first_name" varchar(255), + "last_name" varchar(255), + "email" varchar(255) NOT NULL, + "image_url" varchar(255), + "created_at" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, + "deleted_at" timestamptz, + "role" varchar(64) NOT NULL DEFAULT 'member'::character varying, + "external_id" varchar(255), + PRIMARY KEY ("id") +); + +-- Table Definition +CREATE TABLE "providers" ( + "id" uuid NOT NULL DEFAULT uuid_generate_v4(), + "project_id" uuid NOT NULL, + "type" varchar(255) NOT NULL, + "group" varchar(255) NOT NULL, + "data" jsonb, + "is_default" bool DEFAULT false, + "rate_limit" int4, + "rate_interval" varchar(12) DEFAULT 'second'::character varying, + "created_at" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, + "name" varchar(255) NOT NULL, + "deleted_at" timestamptz, + "external_id" varchar(255), + PRIMARY KEY ("id") +); + +-- Table Definition +CREATE TABLE "projects" ( + "id" uuid NOT NULL DEFAULT uuid_generate_v4(), + "organization_id" uuid, + "name" varchar(255) DEFAULT ''::character varying, + "description" varchar(2048) DEFAULT ''::character varying, + "created_at" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP, + "deleted_at" timestamptz, + "timezone" varchar(50), + "text_opt_out_message" varchar(255), + "link_wrap_email" bool DEFAULT false, + "text_help_message" varchar(255), + "link_wrap_push" bool DEFAULT false, + "tools" _text, + "locale" text, + PRIMARY KEY ("id") +); + +ALTER TABLE "access_tokens" ADD FOREIGN KEY ("admin_id") REFERENCES "admins"("id") ON DELETE CASCADE; + + +-- Indices +CREATE INDEX access_tokens_token_idx ON public.access_tokens USING btree (token); +CREATE INDEX access_tokens_admin_id_idx ON public.access_tokens USING btree (admin_id); +ALTER TABLE "audits" ADD FOREIGN KEY ("admin_id") REFERENCES "admins"("id") ON DELETE CASCADE; +ALTER TABLE "audits" ADD FOREIGN KEY ("project_id") REFERENCES "projects"("id") ON DELETE CASCADE; + + +-- Indices +CREATE INDEX audits_project_id_idx ON public.audits USING btree (project_id); +CREATE INDEX audits_admin_id_idx ON public.audits USING btree (admin_id); +CREATE INDEX audits_event_idx ON public.audits USING btree (event); +CREATE INDEX audits_item_type_item_id_idx ON public.audits USING btree (item_type, item_id); +CREATE INDEX audits_created_at_idx ON public.audits USING btree (created_at); + + +-- Indices +CREATE UNIQUE INDEX job_locks_key_uniq ON public.job_locks USING btree (key); +ALTER TABLE "campaigns" ADD FOREIGN KEY ("provider_id") REFERENCES "providers"("id") ON DELETE CASCADE; +ALTER TABLE "campaigns" ADD FOREIGN KEY ("project_id") REFERENCES "projects"("id") ON DELETE CASCADE; +ALTER TABLE "campaigns" ADD FOREIGN KEY ("subscription_id") REFERENCES "subscriptions"("id") ON DELETE CASCADE; + + +-- Indices +CREATE INDEX campaigns_project_id_idx ON public.campaigns USING btree (project_id); +CREATE INDEX campaigns_subscription_id_idx ON public.campaigns USING btree (subscription_id); +CREATE INDEX campaigns_send_at_idx ON public.campaigns USING btree (send_at); +CREATE INDEX campaigns_project_state_idx ON public.campaigns USING btree (project_id, state); +CREATE INDEX campaigns_provider_id_idx ON public.campaigns USING btree (provider_id); +ALTER TABLE "campaign_sends" ADD FOREIGN KEY ("campaign_id") REFERENCES "campaigns"("id") ON DELETE CASCADE; +ALTER TABLE "campaign_sends" ADD FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE; + + +-- Indices +CREATE INDEX campaign_sends_user_id_idx ON public.campaign_sends USING btree (user_id); +CREATE INDEX campaign_sends_send_at_idx ON public.campaign_sends USING btree (send_at); +CREATE INDEX campaign_sends_state_idx ON public.campaign_sends USING btree (state); +CREATE INDEX campaign_sends_campaign_state_idx ON public.campaign_sends USING btree (campaign_id, state); +ALTER TABLE "devices" ADD FOREIGN KEY ("project_id") REFERENCES "projects"("id") ON DELETE CASCADE; +ALTER TABLE "devices" ADD FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE; + + +-- Indices +CREATE UNIQUE INDEX devices_project_device_uniq ON public.devices USING btree (project_id, device_id); +CREATE UNIQUE INDEX devices_project_token_uniq ON public.devices USING btree (project_id, token); +CREATE INDEX devices_user_id_idx ON public.devices USING btree (user_id); +ALTER TABLE "entity_tags" ADD FOREIGN KEY ("tag_id") REFERENCES "tags"("id") ON DELETE CASCADE; + + +-- Indices +CREATE INDEX entity_tags_tag_id_idx ON public.entity_tags USING btree (tag_id); +CREATE INDEX entity_tags_entity_entity_id_idx ON public.entity_tags USING btree (entity, entity_id); +ALTER TABLE "images" ADD FOREIGN KEY ("project_id") REFERENCES "projects"("id") ON DELETE CASCADE; + + +-- Indices +CREATE INDEX images_project_id_idx ON public.images USING btree (project_id); +CREATE UNIQUE INDEX images_project_uuid_uniq ON public.images USING btree (project_id, id); +ALTER TABLE "journey_steps" ADD FOREIGN KEY ("child_id") REFERENCES "journey_steps"("id") ON DELETE SET NULL; +ALTER TABLE "journey_steps" ADD FOREIGN KEY ("journey_id") REFERENCES "journeys"("id") ON DELETE CASCADE; + + +-- Indices +CREATE INDEX journey_steps_child_id_idx ON public.journey_steps USING btree (child_id); +CREATE UNIQUE INDEX journey_steps_journey_external_uniq ON public.journey_steps USING btree (journey_id, external_id); +ALTER TABLE "journey_step_child" ADD FOREIGN KEY ("step_id") REFERENCES "journey_steps"("id") ON DELETE CASCADE; +ALTER TABLE "journey_step_child" ADD FOREIGN KEY ("child_id") REFERENCES "journey_steps"("id") ON DELETE CASCADE; + + +-- Indices +CREATE INDEX journey_step_child_child_id_idx ON public.journey_step_child USING btree (child_id); +CREATE UNIQUE INDEX journey_step_child_step_child_uniq ON public.journey_step_child USING btree (step_id, child_id); +ALTER TABLE "journey_user_step" ADD FOREIGN KEY ("journey_id") REFERENCES "journeys"("id") ON DELETE CASCADE; +ALTER TABLE "journey_user_step" ADD FOREIGN KEY ("entrance_id") REFERENCES "journey_user_step"("id") ON DELETE CASCADE; +ALTER TABLE "journey_user_step" ADD FOREIGN KEY ("step_id") REFERENCES "journey_steps"("id") ON DELETE CASCADE; +ALTER TABLE "journey_user_step" ADD FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE; + + +-- Indices +CREATE INDEX journey_user_step_step_id_idx ON public.journey_user_step USING btree (step_id); +CREATE INDEX journey_user_step_entrance_id_idx ON public.journey_user_step USING btree (entrance_id); +CREATE INDEX journey_user_step_user_id_idx ON public.journey_user_step USING btree (user_id); +CREATE INDEX journey_user_step_journey_type_delay_idx ON public.journey_user_step USING btree (journey_id, type, delay_until); +CREATE INDEX journey_user_step_ref_idx ON public.journey_user_step USING btree (ref); +CREATE INDEX journey_user_step_delay_until_notnull_idx ON public.journey_user_step USING btree (delay_until) WHERE (delay_until IS NOT NULL); +CREATE INDEX journey_user_step_user_journey_idx ON public.journey_user_step USING btree (user_id, journey_id); +CREATE INDEX user_journey_step_entrance_id_index ON public.journey_user_step USING btree (entrance_id); +CREATE INDEX user_journey_step_journey_id_index ON public.journey_user_step USING btree (journey_id); +ALTER TABLE "journeys" ADD FOREIGN KEY ("parent_id") REFERENCES "journeys"("id") ON DELETE CASCADE; +ALTER TABLE "journeys" ADD FOREIGN KEY ("project_id") REFERENCES "projects"("id") ON DELETE CASCADE; + + +-- Indices +CREATE INDEX journeys_project_id_idx ON public.journeys USING btree (project_id); +CREATE INDEX journeys_parent_id_idx ON public.journeys USING btree (parent_id); +ALTER TABLE "lists" ADD FOREIGN KEY ("project_id") REFERENCES "projects"("id") ON DELETE CASCADE; +ALTER TABLE "lists" ADD FOREIGN KEY ("rule_id") REFERENCES "rules"("id") ON DELETE SET NULL; + + +-- Indices +CREATE INDEX lists_project_id_idx ON public.lists USING btree (project_id); +CREATE INDEX lists_rule_id_idx ON public.lists USING btree (rule_id); +CREATE INDEX lists_project_active_idx ON public.lists USING btree (project_id) WHERE (deleted_at IS NULL); +ALTER TABLE "locales" ADD FOREIGN KEY ("project_id") REFERENCES "projects"("id") ON DELETE CASCADE; + + +-- Indices +CREATE INDEX locales_project_id_idx ON public.locales USING btree (project_id); +ALTER TABLE "notifications" ADD FOREIGN KEY ("project_id") REFERENCES "projects"("id") ON DELETE CASCADE; +ALTER TABLE "notifications" ADD FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE; + + +-- Indices +CREATE INDEX notifications_user_id_idx ON public.notifications USING btree (user_id); +CREATE INDEX notifications_project_id_idx ON public.notifications USING btree (project_id); +CREATE INDEX notifications_read_at_idx ON public.notifications USING btree (read_at); +CREATE INDEX notifications_expires_at_idx ON public.notifications USING btree (expires_at); +CREATE INDEX notifications_user_unread_idx ON public.notifications USING btree (user_id) WHERE (read_at IS NULL); +ALTER TABLE "project_admins" ADD FOREIGN KEY ("admin_id") REFERENCES "admins"("id") ON DELETE CASCADE; +ALTER TABLE "project_admins" ADD FOREIGN KEY ("project_id") REFERENCES "projects"("id") ON DELETE CASCADE; + + +-- Indices +CREATE INDEX project_admins_project_id_idx ON public.project_admins USING btree (project_id); +CREATE INDEX project_admins_admin_id_idx ON public.project_admins USING btree (admin_id); +ALTER TABLE "project_api_keys" ADD FOREIGN KEY ("project_id") REFERENCES "projects"("id") ON DELETE CASCADE; + + +-- Indices +CREATE UNIQUE INDEX project_api_keys_value_uniq ON public.project_api_keys USING btree (value); +CREATE INDEX project_api_keys_project_id_idx ON public.project_api_keys USING btree (project_id); +ALTER TABLE "users" ADD FOREIGN KEY ("project_id") REFERENCES "projects"("id") ON DELETE CASCADE; + + +-- Indices +CREATE UNIQUE INDEX users_project_anonymous_uniq ON public.users USING btree (project_id, anonymous_id); +CREATE UNIQUE INDEX users_project_external_uniq ON public.users USING btree (project_id, external_id); +CREATE INDEX users_email_idx ON public.users USING btree (email); +CREATE INDEX users_phone_idx ON public.users USING btree (phone); +CREATE INDEX users_external_id_idx ON public.users USING btree (external_id); +CREATE INDEX users_updated_at_idx ON public.users USING btree (updated_at); +CREATE INDEX users_anonymous_id_idx ON public.users USING btree (anonymous_id); +CREATE INDEX users_data_idx ON public.users USING gin (data); +ALTER TABLE "subscriptions" ADD FOREIGN KEY ("project_id") REFERENCES "projects"("id") ON DELETE CASCADE; + + +-- Indices +CREATE INDEX subscriptions_project_id_idx ON public.subscriptions USING btree (project_id); +ALTER TABLE "tags" ADD FOREIGN KEY ("project_id") REFERENCES "projects"("id") ON DELETE CASCADE; + + +-- Indices +CREATE INDEX tags_project_id_idx ON public.tags USING btree (project_id); +ALTER TABLE "rules" ADD FOREIGN KEY ("project_id") REFERENCES "projects"("id") ON DELETE CASCADE; +ALTER TABLE "rules" ADD FOREIGN KEY ("root_id") REFERENCES "rules"("id") ON DELETE CASCADE; +ALTER TABLE "rules" ADD FOREIGN KEY ("parent_id") REFERENCES "rules"("id") ON DELETE CASCADE; + + +-- Indices +CREATE INDEX rules_project_id_idx ON public.rules USING btree (project_id); +CREATE INDEX rules_parent_id_idx ON public.rules USING btree (parent_id); +CREATE INDEX rules_root_id_idx ON public.rules USING btree (root_id); +CREATE INDEX rules_group_idx ON public.rules USING btree ("group"); +CREATE INDEX rules_type_idx ON public.rules USING btree (type); +CREATE INDEX rules_value_idx ON public.rules USING btree (value); +ALTER TABLE "project_rule_paths" ADD FOREIGN KEY ("project_id") REFERENCES "projects"("id") ON DELETE CASCADE; + + +-- Indices +CREATE INDEX project_rule_paths_project_id_idx ON public.project_rule_paths USING btree (project_id); + + +-- Indices +CREATE INDEX resources_project_id_idx ON public.resources USING btree (project_id); +ALTER TABLE "rule_evaluations" ADD FOREIGN KEY ("rule_id") REFERENCES "rules"("id") ON DELETE CASCADE; +ALTER TABLE "rule_evaluations" ADD FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE; + + +-- Indices +CREATE INDEX rule_evaluations_rule_id_idx ON public.rule_evaluations USING btree (rule_id); +CREATE UNIQUE INDEX rule_evaluations_user_rule_uniq ON public.rule_evaluations USING btree (user_id, rule_id); +ALTER TABLE "templates" ADD FOREIGN KEY ("project_id") REFERENCES "projects"("id") ON DELETE CASCADE; +ALTER TABLE "templates" ADD FOREIGN KEY ("campaign_id") REFERENCES "campaigns"("id") ON DELETE CASCADE; + + +-- Indices +CREATE INDEX templates_campaign_id_idx ON public.templates USING btree (campaign_id); +CREATE INDEX templates_project_id_idx ON public.templates USING btree (project_id); +ALTER TABLE "user_events" ADD FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE; +ALTER TABLE "user_events" ADD FOREIGN KEY ("project_id") REFERENCES "projects"("id") ON DELETE CASCADE; + + +-- Indices +CREATE INDEX user_events_project_id_idx ON public.user_events USING btree (project_id); +CREATE INDEX user_events_user_id_idx ON public.user_events USING btree (user_id); +CREATE INDEX user_events_created_at_idx ON public.user_events USING btree (created_at); +CREATE INDEX user_events_name_user_idx ON public.user_events USING btree (name, user_id); +CREATE INDEX user_events_data_idx ON public.user_events USING gin (data); +ALTER TABLE "user_list" ADD FOREIGN KEY ("list_id") REFERENCES "lists"("id") ON DELETE CASCADE; +ALTER TABLE "user_list" ADD FOREIGN KEY ("event_id") REFERENCES "user_events"("id") ON DELETE CASCADE; +ALTER TABLE "user_list" ADD FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE; + + +-- Indices +CREATE UNIQUE INDEX user_list_user_list_uniq ON public.user_list USING btree (user_id, list_id); +CREATE INDEX user_list_version_idx ON public.user_list USING btree (version); +CREATE INDEX user_list_list_id_idx ON public.user_list USING btree (list_id); +CREATE INDEX user_list_event_id_idx ON public.user_list USING btree (event_id); +CREATE INDEX user_list_created_at_idx ON public.user_list USING btree (created_at); +ALTER TABLE "user_subscription" ADD FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE; +ALTER TABLE "user_subscription" ADD FOREIGN KEY ("subscription_id") REFERENCES "subscriptions"("id") ON DELETE CASCADE; + + +-- Indices +CREATE INDEX user_subscription_user_id_idx ON public.user_subscription USING btree (user_id); +CREATE INDEX user_subscription_subscription_id_idx ON public.user_subscription USING btree (subscription_id); +ALTER TABLE "organizations" ADD FOREIGN KEY ("notification_provider_id") REFERENCES "providers"("id") ON DELETE SET NULL; + + +-- Indices +CREATE INDEX organizations_notification_provider_id_idx ON public.organizations USING btree (notification_provider_id); +ALTER TABLE "admins" ADD FOREIGN KEY ("organization_id") REFERENCES "organizations"("id") ON DELETE CASCADE; + + +-- Indices +CREATE INDEX admins_organization_id_idx ON public.admins USING btree (organization_id); +ALTER TABLE "providers" ADD FOREIGN KEY ("project_id") REFERENCES "projects"("id") ON DELETE CASCADE; + + +-- Indices +CREATE INDEX providers_project_id_idx ON public.providers USING btree (project_id); +ALTER TABLE "projects" ADD FOREIGN KEY ("organization_id") REFERENCES "organizations"("id") ON DELETE CASCADE; + + +-- Indices +CREATE INDEX projects_organization_id_idx ON public.projects USING btree (organization_id); +CREATE INDEX projects_name_idx ON public.projects USING btree (name); + +-- Triggers for updated_at +CREATE TRIGGER set_updated_at_access_tokens BEFORE UPDATE ON access_tokens FOR EACH ROW EXECUTE PROCEDURE set_updated_at(); +CREATE TRIGGER set_updated_at_admins BEFORE UPDATE ON admins FOR EACH ROW EXECUTE PROCEDURE set_updated_at(); +CREATE TRIGGER set_updated_at_audits BEFORE UPDATE ON audits FOR EACH ROW EXECUTE PROCEDURE set_updated_at(); +CREATE TRIGGER set_updated_at_campaign_sends BEFORE UPDATE ON campaign_sends FOR EACH ROW EXECUTE PROCEDURE set_updated_at(); +CREATE TRIGGER set_updated_at_campaigns BEFORE UPDATE ON campaigns FOR EACH ROW EXECUTE PROCEDURE set_updated_at(); +CREATE TRIGGER set_updated_at_devices BEFORE UPDATE ON devices FOR EACH ROW EXECUTE PROCEDURE set_updated_at(); +CREATE TRIGGER set_updated_at_entity_tags BEFORE UPDATE ON entity_tags FOR EACH ROW EXECUTE PROCEDURE set_updated_at(); +CREATE TRIGGER set_updated_at_images BEFORE UPDATE ON images FOR EACH ROW EXECUTE PROCEDURE set_updated_at(); +CREATE TRIGGER set_updated_at_job_locks BEFORE UPDATE ON job_locks FOR EACH ROW EXECUTE PROCEDURE set_updated_at(); +CREATE TRIGGER set_updated_at_journey_step_child BEFORE UPDATE ON journey_step_child FOR EACH ROW EXECUTE PROCEDURE set_updated_at(); +CREATE TRIGGER set_updated_at_journey_steps BEFORE UPDATE ON journey_steps FOR EACH ROW EXECUTE PROCEDURE set_updated_at(); +CREATE TRIGGER set_updated_at_journey_user_step BEFORE UPDATE ON journey_user_step FOR EACH ROW EXECUTE PROCEDURE set_updated_at(); +CREATE TRIGGER set_updated_at_journeys BEFORE UPDATE ON journeys FOR EACH ROW EXECUTE PROCEDURE set_updated_at(); +CREATE TRIGGER set_updated_at_lists BEFORE UPDATE ON lists FOR EACH ROW EXECUTE PROCEDURE set_updated_at(); +CREATE TRIGGER set_updated_at_locales BEFORE UPDATE ON locales FOR EACH ROW EXECUTE PROCEDURE set_updated_at(); +CREATE TRIGGER set_updated_at_notifications BEFORE UPDATE ON notifications FOR EACH ROW EXECUTE PROCEDURE set_updated_at(); +CREATE TRIGGER set_updated_at_organizations BEFORE UPDATE ON organizations FOR EACH ROW EXECUTE PROCEDURE set_updated_at(); +CREATE TRIGGER set_updated_at_project_admins BEFORE UPDATE ON project_admins FOR EACH ROW EXECUTE PROCEDURE set_updated_at(); +CREATE TRIGGER set_updated_at_project_api_keys BEFORE UPDATE ON project_api_keys FOR EACH ROW EXECUTE PROCEDURE set_updated_at(); +CREATE TRIGGER set_updated_at_project_rule_paths BEFORE UPDATE ON project_rule_paths FOR EACH ROW EXECUTE PROCEDURE set_updated_at(); +CREATE TRIGGER set_updated_at_projects BEFORE UPDATE ON projects FOR EACH ROW EXECUTE PROCEDURE set_updated_at(); +CREATE TRIGGER set_updated_at_providers BEFORE UPDATE ON providers FOR EACH ROW EXECUTE PROCEDURE set_updated_at(); +CREATE TRIGGER set_updated_at_resources BEFORE UPDATE ON resources FOR EACH ROW EXECUTE PROCEDURE set_updated_at(); +CREATE TRIGGER set_updated_at_rule_evaluations BEFORE UPDATE ON rule_evaluations FOR EACH ROW EXECUTE PROCEDURE set_updated_at(); +CREATE TRIGGER set_updated_at_rules BEFORE UPDATE ON rules FOR EACH ROW EXECUTE PROCEDURE set_updated_at(); +CREATE TRIGGER set_updated_at_subscriptions BEFORE UPDATE ON subscriptions FOR EACH ROW EXECUTE PROCEDURE set_updated_at(); +CREATE TRIGGER set_updated_at_tags BEFORE UPDATE ON tags FOR EACH ROW EXECUTE PROCEDURE set_updated_at(); +CREATE TRIGGER set_updated_at_templates BEFORE UPDATE ON templates FOR EACH ROW EXECUTE PROCEDURE set_updated_at(); +CREATE TRIGGER set_updated_at_user_events BEFORE UPDATE ON user_events FOR EACH ROW EXECUTE PROCEDURE set_updated_at(); +CREATE TRIGGER set_updated_at_user_list BEFORE UPDATE ON user_list FOR EACH ROW EXECUTE PROCEDURE set_updated_at(); +CREATE TRIGGER set_updated_at_user_subscription BEFORE UPDATE ON user_subscription FOR EACH ROW EXECUTE PROCEDURE set_updated_at(); +CREATE TRIGGER set_updated_at_users BEFORE UPDATE ON users FOR EACH ROW EXECUTE PROCEDURE set_updated_at(); diff --git a/services/nexus/internal/store/projects.go b/services/nexus/internal/store/projects.go new file mode 100644 index 00000000..c619e487 --- /dev/null +++ b/services/nexus/internal/store/projects.go @@ -0,0 +1,49 @@ +package store + +import ( + "context" + "time" + + "github.com/google/uuid" + "github.com/lib/pq" +) + +type Project struct { + ID uuid.UUID `db:"id"` + OrganizationID *uuid.UUID `db:"organization_id"` + Name string `db:"name"` + Description *string `db:"description"` + CreatedAt time.Time `db:"created_at"` + UpdatedAt time.Time `db:"updated_at"` + DeletedAt *time.Time `db:"deleted_at"` + Timezone string `db:"timezone"` + TextOptOutMessage *string `db:"text_opt_out_message"` + LinkWrapEmail bool `db:"link_wrap_email"` + TextHelpMessage *string `db:"text_help_message"` + LinkWrapPush bool `db:"link_wrap_push"` + Tools pq.StringArray `db:"tools"` + Locale string `db:"locale"` +} + +func NewProjectsStore(db DB) *ProjectsStore { + return &ProjectsStore{db: db} +} + +type ProjectsStore struct { + db DB +} + +func (s *ProjectsStore) GetProject(ctx context.Context, id uuid.UUID) (*Project, error) { + query := ` + SELECT * FROM projects + WHERE id = $1 + AND deleted_at IS NULL` + + var project Project + err := s.db.GetContext(ctx, &project, query, id) + if err != nil { + return nil, err + } + + return &project, nil +} diff --git a/services/nexus/internal/store/providers.go b/services/nexus/internal/store/providers.go new file mode 100644 index 00000000..dfbb0bbf --- /dev/null +++ b/services/nexus/internal/store/providers.go @@ -0,0 +1,94 @@ +package store + +import ( + "context" + "encoding/json" + "time" + + "github.com/google/uuid" + "github.com/lunogram/platform/services/nexus/oapi" +) + +type Provider struct { + ID uuid.UUID `db:"id"` + ProjectID uuid.UUID `db:"project_id"` + Type string `db:"type"` + Group string `db:"group"` + Data json.RawMessage `db:"data"` + IsDefault bool `db:"is_default"` + RateLimit *int32 `db:"rate_limit"` + RateInterval *string `db:"rate_interval"` + Name string `db:"name"` + CreatedAt time.Time `db:"created_at"` + UpdatedAt time.Time `db:"updated_at"` +} + +func (provider *Provider) OAPI() *oapi.Provider { + if provider == nil { + return nil + } + + result := oapi.Provider{ + Id: provider.ID, + Data: &provider.Data, + Group: oapi.ProviderGroup(provider.Group), + IsDefault: provider.IsDefault, + Name: provider.Name, + ProjectId: provider.ProjectID, + Type: provider.Type, + CreatedAt: provider.CreatedAt, + UpdatedAt: provider.UpdatedAt, + } + + if provider.RateLimit != nil { + result.RateLimit = provider.RateLimit + } + + if provider.RateInterval != nil { + interval := oapi.ProviderRateInterval(*provider.RateInterval) + result.RateInterval = &interval + } + + return &result +} + +func NewProvidersStore(db DB) *ProvidersStore { + return &ProvidersStore{db: db} +} + +type ProvidersStore struct { + db DB +} + +func (s *ProvidersStore) GetProvider(ctx context.Context, id uuid.UUID) (*Provider, error) { + query := ` + SELECT id, project_id, type, "group", data, is_default, rate_limit, rate_interval, created_at, updated_at, name + FROM providers + WHERE id = $1` + + var provider Provider + err := s.db.GetContext(ctx, &provider, query, id) + if err != nil { + return nil, err + } + + return &provider, nil +} + +func (s *ProvidersStore) GetDefaultProviderChannel(ctx context.Context, projectID uuid.UUID, group string) (*Provider, error) { + query := ` + SELECT id, project_id, type, "group", data, is_default, rate_limit, rate_interval, created_at, updated_at, name + FROM providers + WHERE project_id = $1 + AND "group" = $2 + AND is_default = true + LIMIT 1` + + var provider Provider + err := s.db.GetContext(ctx, &provider, query, projectID, group) + if err != nil { + return nil, err + } + + return &provider, nil +} diff --git a/services/nexus/internal/store/store.go b/services/nexus/internal/store/store.go new file mode 100644 index 00000000..1d52921c --- /dev/null +++ b/services/nexus/internal/store/store.go @@ -0,0 +1,86 @@ +package store + +import ( + "context" + "database/sql" + "database/sql/driver" + "encoding/json" + "errors" + "fmt" + + "github.com/cloudproud/graceful" + _ "github.com/jackc/pgx/v5/stdlib" + "github.com/jmoiron/sqlx" +) + +var ErrNoRows = sql.ErrNoRows + +type Config struct { + URI string `env:"POSTGRES_URI" envDefault:"postgres://postgres:password@postgres:5432/postgres?sslmode=disable"` +} + +func Connect(ctx graceful.Context, conf Config) (*sqlx.DB, error) { + db, err := sqlx.Connect("pgx", conf.URI) + if err != nil { + return nil, fmt.Errorf("failed to connect to database: %w", err) + } + + return db, nil +} + +type DB interface { + sqlx.Ext + sqlx.ExtContext + Get(dest interface{}, query string, args ...interface{}) error + GetContext(ctx context.Context, dest interface{}, query string, args ...interface{}) error + Select(dest interface{}, query string, args ...interface{}) error + SelectContext(ctx context.Context, dest interface{}, query string, args ...interface{}) error + QueryRow(query string, args ...any) *sql.Row + QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row + NamedExec(query string, arg interface{}) (sql.Result, error) + NamedExecContext(ctx context.Context, query string, arg interface{}) (sql.Result, error) +} + +func NewStores(db DB) *Stores { + return &Stores{ + ProjectsStore: NewProjectsStore(db), + CampaignsStore: NewCampaignsStore(db), + ProvidersStore: NewProvidersStore(db), + TemplatesStore: NewTemplatesStore(db), + } +} + +type Stores struct { + *ProjectsStore + *CampaignsStore + *ProvidersStore + *TemplatesStore +} + +type Pagination struct { + Limit int + Offset int +} + +type JSONB[T any] struct { + Data T +} + +// Scan implements sql.Scanner for reading from database +func (j *JSONB[T]) Scan(value interface{}) error { + if value == nil { + return nil + } + + bytes, ok := value.([]byte) + if !ok { + return errors.New("failed to scan JSONB: expected []byte") + } + + return json.Unmarshal(bytes, &j.Data) +} + +// Value implements driver.Valuer for writing to database +func (j JSONB[T]) Value() (driver.Value, error) { + return json.Marshal(j.Data) +} diff --git a/services/nexus/internal/store/templates.go b/services/nexus/internal/store/templates.go new file mode 100644 index 00000000..2bffeb55 --- /dev/null +++ b/services/nexus/internal/store/templates.go @@ -0,0 +1,102 @@ +package store + +import ( + "context" + "encoding/json" + "time" + + "github.com/google/uuid" + "github.com/lunogram/platform/services/nexus/oapi" +) + +type Templates []Template + +func (templates Templates) OAPI() []oapi.Template { + result := make([]oapi.Template, len(templates)) + for index, template := range templates { + result[index] = template.OAPI() + } + return result +} + +type Template struct { + ID uuid.UUID `db:"id"` + CampaignID uuid.UUID `db:"campaign_id"` + ProjectID uuid.UUID `db:"project_id"` + Type string `db:"type"` + Data json.RawMessage `db:"data"` + Locale string `db:"locale"` + CreatedAt time.Time `db:"created_at"` + UpdatedAt time.Time `db:"updated_at"` +} + +func (template Template) OAPI() oapi.Template { + return oapi.Template{ + Id: template.ID, + CampaignId: template.CampaignID, + Type: oapi.TemplateType(template.Type), + Data: template.Data, + Locale: template.Locale, + ProjectId: template.ProjectID, + UpdatedAt: template.UpdatedAt, + CreatedAt: template.CreatedAt, + } +} + +func NewTemplatesStore(db DB) *TemplatesStore { + return &TemplatesStore{db: db} +} + +type TemplatesStore struct { + db DB +} + +func (s *TemplatesStore) CreateTemplate(ctx context.Context, projectID, campaignID uuid.UUID, channel string, locale string) (uuid.UUID, error) { + // TODO: remove channel type, this type is needed within the "legacy" NodeJS back-end + stmt := ` + INSERT INTO templates (project_id, campaign_id, type, locale) + VALUES ($1, $2, $3, $4) + RETURNING id` + + var id uuid.UUID + err := s.db.QueryRowContext(ctx, stmt, projectID, campaignID, channel, locale).Scan(&id) + if err != nil { + return uuid.Nil, err + } + + return id, nil +} + +func (s *TemplatesStore) GetTemplate(ctx context.Context, projectID, templateID uuid.UUID) (*Template, error) { + query := ` + SELECT templates.id, templates.project_id, templates.campaign_id, campaigns.channel AS type, templates.data, templates.locale, templates.created_at, templates.updated_at + FROM templates + JOIN campaigns ON templates.campaign_id = campaigns.id + WHERE templates.project_id = $1 + AND templates.id = $2` + + var template Template + err := s.db.GetContext(ctx, &template, query, projectID, templateID) + if err != nil { + return nil, err + } + + return &template, nil +} + +func (s *TemplatesStore) ListTemplates(ctx context.Context, projectID, campaignID uuid.UUID) ([]Template, error) { + query := ` + SELECT templates.id, templates.project_id, templates.campaign_id, campaigns.channel AS type, templates.data, templates.locale, templates.created_at, templates.updated_at + FROM templates + JOIN campaigns ON templates.campaign_id = campaigns.id + WHERE templates.project_id = $1 + AND templates.campaign_id = $2` + + var templates []Template + err := s.db.SelectContext(ctx, &templates, query, projectID, campaignID) + if err != nil { + return nil, err + } + + return templates, nil +} diff --git a/services/nexus/main.go b/services/nexus/main.go new file mode 100644 index 00000000..750187e4 --- /dev/null +++ b/services/nexus/main.go @@ -0,0 +1,72 @@ +package main + +import ( + "context" + "flag" + "fmt" + "os" + + "github.com/caarlos0/env/v10" + "github.com/cloudproud/graceful" + "github.com/lunogram/platform/services/nexus/internal/config" + "github.com/lunogram/platform/services/nexus/internal/http" + "github.com/lunogram/platform/services/nexus/internal/store" + "go.uber.org/zap" +) + +var migrate bool + +func init() { + flag.BoolVar(&migrate, "migrate", false, "flag indicating whether the service should run migrations and exit") +} + +func main() { + if err := run(); err != nil { + fmt.Println(err) + os.Exit(1) + } +} + +func run() error { + flag.Parse() + ctx := graceful.NewContext(context.Background()) + + logger, err := zap.NewDevelopment() + if err != nil { + return err + } + defer logger.Sync() //nolint:errcheck + + conf := config.Service{} + err = env.Parse(&conf) + if err != nil { + return err + } + + if migrate { + logger.Info("running database migrations...") + return store.Migrate(conf.Store) + } + + logger.Info("starting service...") + logger.Info("connecting to database") + + db, err := store.Connect(ctx, conf.Store) + if err != nil { + return err + } + + logger.Info("starting http server") + + srv, err := http.NewServer(ctx, logger, conf, db) + if err != nil { + return err + } + + logger.Info("serving http server") + go srv.Serve(ctx, conf.Address) + + logger.Info("service up and running!") + ctx.AwaitKillSignal() + return nil +} diff --git a/services/nexus/oapi/middleware.go b/services/nexus/oapi/middleware.go new file mode 100644 index 00000000..7207c32c --- /dev/null +++ b/services/nexus/oapi/middleware.go @@ -0,0 +1,40 @@ +package oapi + +import ( + "errors" + "net/http" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/getkin/kin-openapi/openapi3filter" + "github.com/lunogram/platform/pkg/http/problem" + "github.com/lunogram/platform/pkg/http/scalar" + middleware "github.com/oapi-codegen/nethttp-middleware" +) + +func Validator(spec *openapi3.T, options openapi3filter.Options) func(next http.Handler) http.Handler { + return middleware.OapiRequestValidatorWithOptions(spec, &middleware.Options{ + Options: options, + DoNotValidateServers: true, + ErrorHandler: func(w http.ResponseWriter, message string, statusCode int) { + err := problem.WithStatus(problem.WithDescription(errors.New(message), "bad request", message), statusCode) + WriteProblem(w, err) + }, + }) +} + +func Scalar() func(next http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + if req.URL.Path == "/openapi.yaml" { + scalar.HandleOAPI(oapi).ServeHTTP(w, req) + return + } + if req.URL.Path == "/" { + http.FileServer(http.FS(scalar.FS)).ServeHTTP(w, req) + return + } + + next.ServeHTTP(w, req) + }) + } +} diff --git a/services/nexus/oapi/oapi.go b/services/nexus/oapi/oapi.go new file mode 100644 index 00000000..8bde3291 --- /dev/null +++ b/services/nexus/oapi/oapi.go @@ -0,0 +1,74 @@ +package oapi + +import ( + _ "embed" + "net/http" + "strings" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/lunogram/platform/pkg/http/json" + "github.com/lunogram/platform/pkg/http/problem" +) + +//go:generate oapi-codegen -o ./resources_gen.go -generate types,client,chi-server -package oapi ./resources.yml + +//go:embed resources.yml +var oapi []byte + +func Spec() (*openapi3.T, error) { + return openapi3.NewLoader().LoadFromData(oapi) +} + +// WriteProblem writes a JSON v1 problem message to the given response writer. +// The problem status, description, and other properties are unwrapped from the +// given error. +func WriteProblem(w http.ResponseWriter, err error) { + title, description := problem.GetDescription(err) + status := problem.GetStatus(err) + result := Problem{ + Title: title, + Detail: description, + } + + if result.Title == "" { + result.Title = strings.ToLower(http.StatusText(status)) + } + + json.Write(w, status, result) +} + +type PaginationLimit int + +func (limit *PaginationLimit) ToInt() int { + if limit == nil { + return 20 + } + + result := int(*limit) + + if result > 100 { + result = 100 + } + + if result < 0 { + result = 20 + } + + return result +} + +type PaginationOffset int + +func (offset *PaginationOffset) ToInt() int { + if offset == nil { + return 0 + } + + result := int(*offset) + + if result < 0 { + result = 0 + } + + return result +} diff --git a/services/nexus/oapi/resources.yml b/services/nexus/oapi/resources.yml new file mode 100644 index 00000000..4c1a899f --- /dev/null +++ b/services/nexus/oapi/resources.yml @@ -0,0 +1,542 @@ +openapi: 3.0.3 +info: + title: Lunogram Platform API + description: API for managing campaigns, templates, and providers + version: 1.0.0 + +paths: + /api/admin/projects/{projectID}/campaigns: + get: + summary: List campaigns + description: Retrieves a list of campaigns with optional filtering + operationId: listCampaigns + tags: + - Campaigns + security: + - HttpBearerAuth: [] + parameters: + - name: projectID + in: path + required: true + schema: + type: string + format: uuid + description: The project ID + - $ref: '#/components/parameters/Limit' + - $ref: '#/components/parameters/Offset' + responses: + '200': + $ref: '#/components/responses/CampaignListResponse' + default: + $ref: '#/components/responses/Error' + + post: + summary: Create campaign + description: Creates a new campaign + operationId: createCampaign + tags: + - Campaigns + security: + - HttpBearerAuth: [] + parameters: + - name: projectID + in: path + required: true + schema: + type: string + format: uuid + description: The project ID + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CreateCampaign' + responses: + '201': + description: Campaign created successfully + content: + application/json: + schema: + $ref: '#/components/schemas/Campaign' + default: + $ref: '#/components/responses/Error' + + /api/admin/projects/{projectID}/campaigns/{campaignID}: + get: + summary: Get campaign by ID + description: Retrieves a specific campaign with its templates and provider information + operationId: getCampaign + tags: + - Campaigns + security: + - HttpBearerAuth: [] + parameters: + - name: projectID + in: path + required: true + schema: + type: string + format: uuid + description: The project ID + - name: campaignID + in: path + required: true + schema: + type: string + format: uuid + description: The campaign ID + responses: + '200': + description: Campaign retrieved successfully + content: + application/json: + schema: + type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/Campaign' + default: + $ref: '#/components/responses/Error' + + patch: + summary: Update campaign + description: Updates campaign properties such as name and provider + operationId: updateCampaign + tags: + - Campaigns + security: + - HttpBearerAuth: [] + parameters: + - name: projectID + in: path + required: true + schema: + type: string + format: uuid + description: The project ID + - name: campaignID + in: path + required: true + schema: + type: string + format: uuid + description: The campaign ID + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UpdateCampaign' + responses: + '200': + description: Campaign updated successfully + content: + application/json: + schema: + $ref: '#/components/schemas/Campaign' + default: + $ref: '#/components/responses/Error' + + /api/admin/projects/{projectID}/templates/{templateID}: + get: + summary: Get template by ID + description: Retrieves a specific template with its content and editor data + operationId: getTemplate + tags: + - Templates + security: + - HttpBearerAuth: [] + parameters: + - name: projectID + in: path + required: true + schema: + type: string + format: uuid + description: The project ID + - name: templateID + in: path + required: true + schema: + type: string + format: uuid + description: The template ID + responses: + '200': + description: Template retrieved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/Template' + default: + $ref: '#/components/responses/Error' + +components: + parameters: + Limit: + name: limit + in: query + required: false + schema: + type: integer + minimum: 1 + maximum: 100 + default: 20 + x-go-type: PaginationLimit + description: Maximum number of items to return + + Offset: + name: offset + in: query + required: false + schema: + type: integer + minimum: 0 + default: 0 + x-go-type: PaginationOffset + description: Number of items to skip + + responses: + Error: + description: Error response + content: + application/json: + schema: + $ref: '#/components/schemas/Problem' + + CampaignListResponse: + description: Campaigns retrieved successfully + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PaginatedResponse' + - type: object + required: + - results + properties: + results: + type: array + items: + $ref: '#/components/schemas/Campaign' + + schemas: + Problem: + type: object + required: + - title + - detail + properties: + title: + type: string + description: | + A short summary of the problem type. Written in English and readable for engineers, usually not suited for non technical stakeholders and not localized. + example: some title for the error situation + detail: + type: string + description: | + A human readable explanation specific to this occurrence of the problem that is helpful to locate the problem and give advice on how to proceed. Written in English and readable for engineers, usually not suited for non technical stakeholders and not localized. + example: some description for the error situation + + PaginatedResponse: + type: object + required: + - total + - limit + - offset + properties: + total: + type: integer + description: Total number of items matching the filters + example: 42 + limit: + type: integer + description: Maximum number of items returned + example: 20 + offset: + type: integer + description: Number of items skipped + example: 0 + + CreateCampaign: + type: object + required: + - name + - channel + properties: + name: + type: string + example: "Welcome Campaign" + channel: + type: string + enum: [email, sms, push] + example: "email" + provider_id: + type: string + format: uuid + example: "5143f27c-cca9-4dc4-9059-e1dbb08144ad" + subscription_id: + type: string + format: uuid + nullable: true + + UpdateCampaign: + type: object + properties: + name: + type: string + example: "epic hopper" + provider_id: + type: string + format: uuid + example: "5143f27c-cca9-4dc4-9059-e1dbb08144ad" + + Campaign: + type: object + required: + - id + - name + - project_id + - channel + - templates + - delivery + - created_at + - updated_at + properties: + created_at: + type: string + format: date-time + example: "2025-11-19T14:18:42.960Z" + updated_at: + type: string + format: date-time + example: "2025-11-23T17:20:00.021Z" + id: + type: string + format: uuid + example: "52f3f921-1343-48af-b795-87c0fd3b44aa" + project_id: + type: string + format: uuid + name: + type: string + example: "Welcome to the program!" + channel: + type: string + enum: [email, sms, push] + example: "email" + subscription_id: + type: string + format: uuid + nullable: true + provider: + $ref: '#/components/schemas/Provider' + templates: + type: array + items: + $ref: '#/components/schemas/Template' + delivery: + $ref: '#/components/schemas/Delivery' + + Delivery: + type: object + required: + - sent + - opens + - total + - clicks + properties: + sent: + type: integer + example: 0 + opens: + type: integer + example: 0 + total: + type: integer + example: 0 + clicks: + type: integer + example: 0 + + Provider: + type: object + required: + - id + - type + - name + - project_id + - group + - is_default + - created_at + - updated_at + properties: + id: + type: string + format: uuid + example: "5143f27c-cca9-4dc4-9059-e1dbb08144ad" + type: + type: string + example: "webhook" + name: + type: string + example: "Lunogram" + project_id: + type: string + format: uuid + example: "4c9d3163-7b64-4f9e-9068-d2e4b96be56b" + group: + type: string + enum: [email, sms, push] + example: "email" + data: + oneOf: + - $ref: '#/components/schemas/EmailProviderData' + - $ref: '#/components/schemas/SmsProviderData' + - $ref: '#/components/schemas/PushProviderData' + x-go-type: json.RawMessage + is_default: + type: boolean + example: true + rate_limit: + type: integer + example: 0 + x-go-type: int32 + rate_interval: + type: string + enum: [second, minute, hour, day] + example: "second" + created_at: + type: string + format: date-time + example: "2025-11-05T13:38:03.861Z" + updated_at: + type: string + format: date-time + example: "2025-11-11T13:58:40.657Z" + + Template: + type: object + required: + - id + - project_id + - campaign_id + - data + - type + - locale + - created_at + - updated_at + properties: + created_at: + type: string + format: date-time + example: "2025-11-19T14:18:43.020Z" + updated_at: + type: string + format: date-time + example: "2025-11-24T10:06:34.082Z" + id: + type: string + format: uuid + example: "4fe0d7e3-3071-40aa-a3ee-ecfb8e26a269" + project_id: + type: string + format: uuid + example: "4c9d3163-7b64-4f9e-9068-d2e4b96be56b" + campaign_id: + type: string + format: uuid + example: "52f3f921-1343-48af-b795-87c0fd3b44aa" + type: + type: string + enum: [email, sms, push] + example: "email" + data: + oneOf: + - $ref: '#/components/schemas/EmailTemplateData' + - $ref: '#/components/schemas/SmsTemplateData' + - $ref: '#/components/schemas/PushTemplateData' + x-go-type: json.RawMessage + locale: + type: string + example: "en" + + EmailTemplateData: + type: object + properties: + from: + type: object + subject: + type: string + example: "Quick question for you..." + html: + type: string + description: HTML content of the email template + editor: + type: object + description: Editor configuration and content structure + properties: + root: + type: object + properties: + props: + type: object + zones: + type: object + content: + type: array + items: + type: object + + SmsTemplateData: + type: object + properties: + body: + type: string + description: SMS message body + example: "Your verification code is: {{code}}" + + PushTemplateData: + type: object + properties: + title: + type: string + description: Push notification title + example: "New message" + body: + type: string + description: Push notification body + example: "You have a new message from {{sender}}" + data: + type: object + description: Additional data payload for the push notification + + EmailProviderData: + type: object + properties: + default_from: + type: string + example: "8f0512c2-e606-4a58-95cb-60a4a7f859e1@campaign.lunogram.com" + default_from_name: + type: string + example: "Onboarding" + default_from_locked: + type: boolean + example: true + + SmsProviderData: + type: object + properties: {} + + PushProviderData: + type: object + properties: {} + + securitySchemes: + HttpBearerAuth: + type: http + scheme: bearer + bearerFormat: JWT \ No newline at end of file diff --git a/services/nexus/oapi/resources_gen.go b/services/nexus/oapi/resources_gen.go new file mode 100644 index 00000000..18f7f5a9 --- /dev/null +++ b/services/nexus/oapi/resources_gen.go @@ -0,0 +1,1479 @@ +// Package oapi provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.5.1 DO NOT EDIT. +package oapi + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "strings" + "time" + + "github.com/go-chi/chi/v5" + "github.com/oapi-codegen/runtime" + openapi_types "github.com/oapi-codegen/runtime/types" +) + +const ( + HttpBearerAuthScopes = "HttpBearerAuth.Scopes" +) + +// Defines values for CampaignChannel. +const ( + CampaignChannelEmail CampaignChannel = "email" + CampaignChannelPush CampaignChannel = "push" + CampaignChannelSms CampaignChannel = "sms" +) + +// Defines values for CreateCampaignChannel. +const ( + CreateCampaignChannelEmail CreateCampaignChannel = "email" + CreateCampaignChannelPush CreateCampaignChannel = "push" + CreateCampaignChannelSms CreateCampaignChannel = "sms" +) + +// Defines values for ProviderGroup. +const ( + ProviderGroupEmail ProviderGroup = "email" + ProviderGroupPush ProviderGroup = "push" + ProviderGroupSms ProviderGroup = "sms" +) + +// Defines values for ProviderRateInterval. +const ( + Day ProviderRateInterval = "day" + Hour ProviderRateInterval = "hour" + Minute ProviderRateInterval = "minute" + Second ProviderRateInterval = "second" +) + +// Defines values for TemplateType. +const ( + TemplateTypeEmail TemplateType = "email" + TemplateTypePush TemplateType = "push" + TemplateTypeSms TemplateType = "sms" +) + +// Campaign defines model for Campaign. +type Campaign struct { + Channel CampaignChannel `json:"channel"` + CreatedAt time.Time `json:"created_at"` + Delivery Delivery `json:"delivery"` + Id openapi_types.UUID `json:"id"` + Name string `json:"name"` + ProjectId openapi_types.UUID `json:"project_id"` + Provider *Provider `json:"provider,omitempty"` + SubscriptionId *openapi_types.UUID `json:"subscription_id"` + Templates []Template `json:"templates"` + UpdatedAt time.Time `json:"updated_at"` +} + +// CampaignChannel defines model for Campaign.Channel. +type CampaignChannel string + +// CreateCampaign defines model for CreateCampaign. +type CreateCampaign struct { + Channel CreateCampaignChannel `json:"channel"` + Name string `json:"name"` + ProviderId *openapi_types.UUID `json:"provider_id,omitempty"` + SubscriptionId *openapi_types.UUID `json:"subscription_id"` +} + +// CreateCampaignChannel defines model for CreateCampaign.Channel. +type CreateCampaignChannel string + +// Delivery defines model for Delivery. +type Delivery struct { + Clicks int `json:"clicks"` + Opens int `json:"opens"` + Sent int `json:"sent"` + Total int `json:"total"` +} + +// EmailProviderData defines model for EmailProviderData. +type EmailProviderData struct { + DefaultFrom *string `json:"default_from,omitempty"` + DefaultFromLocked *bool `json:"default_from_locked,omitempty"` + DefaultFromName *string `json:"default_from_name,omitempty"` +} + +// EmailTemplateData defines model for EmailTemplateData. +type EmailTemplateData struct { + // Editor Editor configuration and content structure + Editor *struct { + Content *[]map[string]interface{} `json:"content,omitempty"` + Root *struct { + Props *map[string]interface{} `json:"props,omitempty"` + } `json:"root,omitempty"` + Zones *map[string]interface{} `json:"zones,omitempty"` + } `json:"editor,omitempty"` + From *map[string]interface{} `json:"from,omitempty"` + + // Html HTML content of the email template + Html *string `json:"html,omitempty"` + Subject *string `json:"subject,omitempty"` +} + +// PaginatedResponse defines model for PaginatedResponse. +type PaginatedResponse struct { + // Limit Maximum number of items returned + Limit int `json:"limit"` + + // Offset Number of items skipped + Offset int `json:"offset"` + + // Total Total number of items matching the filters + Total int `json:"total"` +} + +// Problem defines model for Problem. +type Problem struct { + // Detail A human readable explanation specific to this occurrence of the problem that is helpful to locate the problem and give advice on how to proceed. Written in English and readable for engineers, usually not suited for non technical stakeholders and not localized. + Detail string `json:"detail"` + + // Title A short summary of the problem type. Written in English and readable for engineers, usually not suited for non technical stakeholders and not localized. + Title string `json:"title"` +} + +// Provider defines model for Provider. +type Provider struct { + CreatedAt time.Time `json:"created_at"` + Data *json.RawMessage `json:"data,omitempty"` + Group ProviderGroup `json:"group"` + Id openapi_types.UUID `json:"id"` + IsDefault bool `json:"is_default"` + Name string `json:"name"` + ProjectId openapi_types.UUID `json:"project_id"` + RateInterval *ProviderRateInterval `json:"rate_interval,omitempty"` + RateLimit *int32 `json:"rate_limit,omitempty"` + Type string `json:"type"` + UpdatedAt time.Time `json:"updated_at"` +} + +// ProviderGroup defines model for Provider.Group. +type ProviderGroup string + +// ProviderRateInterval defines model for Provider.RateInterval. +type ProviderRateInterval string + +// PushProviderData defines model for PushProviderData. +type PushProviderData = map[string]interface{} + +// PushTemplateData defines model for PushTemplateData. +type PushTemplateData struct { + // Body Push notification body + Body *string `json:"body,omitempty"` + + // Data Additional data payload for the push notification + Data *map[string]interface{} `json:"data,omitempty"` + + // Title Push notification title + Title *string `json:"title,omitempty"` +} + +// SmsProviderData defines model for SmsProviderData. +type SmsProviderData = map[string]interface{} + +// SmsTemplateData defines model for SmsTemplateData. +type SmsTemplateData struct { + // Body SMS message body + Body *string `json:"body,omitempty"` +} + +// Template defines model for Template. +type Template struct { + CampaignId openapi_types.UUID `json:"campaign_id"` + CreatedAt time.Time `json:"created_at"` + Data json.RawMessage `json:"data"` + Id openapi_types.UUID `json:"id"` + Locale string `json:"locale"` + ProjectId openapi_types.UUID `json:"project_id"` + Type TemplateType `json:"type"` + UpdatedAt time.Time `json:"updated_at"` +} + +// TemplateType defines model for Template.Type. +type TemplateType string + +// UpdateCampaign defines model for UpdateCampaign. +type UpdateCampaign struct { + Name *string `json:"name,omitempty"` + ProviderId *openapi_types.UUID `json:"provider_id,omitempty"` +} + +// Limit defines model for Limit. +type Limit = PaginationLimit + +// Offset defines model for Offset. +type Offset = PaginationOffset + +// CampaignListResponse defines model for CampaignListResponse. +type CampaignListResponse struct { + // Limit Maximum number of items returned + Limit int `json:"limit"` + + // Offset Number of items skipped + Offset int `json:"offset"` + Results []Campaign `json:"results"` + + // Total Total number of items matching the filters + Total int `json:"total"` +} + +// Error defines model for Error. +type Error = Problem + +// ListCampaignsParams defines parameters for ListCampaigns. +type ListCampaignsParams struct { + // Limit Maximum number of items to return + Limit *Limit `form:"limit,omitempty" json:"limit,omitempty"` + + // Offset Number of items to skip + Offset *Offset `form:"offset,omitempty" json:"offset,omitempty"` +} + +// CreateCampaignJSONRequestBody defines body for CreateCampaign for application/json ContentType. +type CreateCampaignJSONRequestBody = CreateCampaign + +// UpdateCampaignJSONRequestBody defines body for UpdateCampaign for application/json ContentType. +type UpdateCampaignJSONRequestBody = UpdateCampaign + +// RequestEditorFn is the function signature for the RequestEditor callback function +type RequestEditorFn func(ctx context.Context, req *http.Request) error + +// Doer performs HTTP requests. +// +// The standard http.Client implements this interface. +type HttpRequestDoer interface { + Do(req *http.Request) (*http.Response, error) +} + +// Client which conforms to the OpenAPI3 specification for this service. +type Client struct { + // The endpoint of the server conforming to this interface, with scheme, + // https://api.deepmap.com for example. This can contain a path relative + // to the server, such as https://api.deepmap.com/dev-test, and all the + // paths in the swagger spec will be appended to the server. + Server string + + // Doer for performing requests, typically a *http.Client with any + // customized settings, such as certificate chains. + Client HttpRequestDoer + + // A list of callbacks for modifying requests which are generated before sending over + // the network. + RequestEditors []RequestEditorFn +} + +// ClientOption allows setting custom parameters during construction +type ClientOption func(*Client) error + +// Creates a new Client, with reasonable defaults +func NewClient(server string, opts ...ClientOption) (*Client, error) { + // create a client with sane default values + client := Client{ + Server: server, + } + // mutate client and add all optional params + for _, o := range opts { + if err := o(&client); err != nil { + return nil, err + } + } + // ensure the server URL always has a trailing slash + if !strings.HasSuffix(client.Server, "/") { + client.Server += "/" + } + // create httpClient, if not already present + if client.Client == nil { + client.Client = &http.Client{} + } + return &client, nil +} + +// WithHTTPClient allows overriding the default Doer, which is +// automatically created using http.Client. This is useful for tests. +func WithHTTPClient(doer HttpRequestDoer) ClientOption { + return func(c *Client) error { + c.Client = doer + return nil + } +} + +// WithRequestEditorFn allows setting up a callback function, which will be +// called right before sending the request. This can be used to mutate the request. +func WithRequestEditorFn(fn RequestEditorFn) ClientOption { + return func(c *Client) error { + c.RequestEditors = append(c.RequestEditors, fn) + return nil + } +} + +// The interface specification for the client above. +type ClientInterface interface { + // ListCampaigns request + ListCampaigns(ctx context.Context, projectID openapi_types.UUID, params *ListCampaignsParams, reqEditors ...RequestEditorFn) (*http.Response, error) + + // CreateCampaignWithBody request with any body + CreateCampaignWithBody(ctx context.Context, projectID openapi_types.UUID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + CreateCampaign(ctx context.Context, projectID openapi_types.UUID, body CreateCampaignJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // GetCampaign request + GetCampaign(ctx context.Context, projectID openapi_types.UUID, campaignID openapi_types.UUID, reqEditors ...RequestEditorFn) (*http.Response, error) + + // UpdateCampaignWithBody request with any body + UpdateCampaignWithBody(ctx context.Context, projectID openapi_types.UUID, campaignID openapi_types.UUID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + UpdateCampaign(ctx context.Context, projectID openapi_types.UUID, campaignID openapi_types.UUID, body UpdateCampaignJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // GetTemplate request + GetTemplate(ctx context.Context, projectID openapi_types.UUID, templateID openapi_types.UUID, reqEditors ...RequestEditorFn) (*http.Response, error) +} + +func (c *Client) ListCampaigns(ctx context.Context, projectID openapi_types.UUID, params *ListCampaignsParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewListCampaignsRequest(c.Server, projectID, params) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) CreateCampaignWithBody(ctx context.Context, projectID openapi_types.UUID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateCampaignRequestWithBody(c.Server, projectID, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) CreateCampaign(ctx context.Context, projectID openapi_types.UUID, body CreateCampaignJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateCampaignRequest(c.Server, projectID, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) GetCampaign(ctx context.Context, projectID openapi_types.UUID, campaignID openapi_types.UUID, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetCampaignRequest(c.Server, projectID, campaignID) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) UpdateCampaignWithBody(ctx context.Context, projectID openapi_types.UUID, campaignID openapi_types.UUID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewUpdateCampaignRequestWithBody(c.Server, projectID, campaignID, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) UpdateCampaign(ctx context.Context, projectID openapi_types.UUID, campaignID openapi_types.UUID, body UpdateCampaignJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewUpdateCampaignRequest(c.Server, projectID, campaignID, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) GetTemplate(ctx context.Context, projectID openapi_types.UUID, templateID openapi_types.UUID, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetTemplateRequest(c.Server, projectID, templateID) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +// NewListCampaignsRequest generates requests for ListCampaigns +func NewListCampaignsRequest(server string, projectID openapi_types.UUID, params *ListCampaignsParams) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "projectID", runtime.ParamLocationPath, projectID) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/api/admin/projects/%s/campaigns", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + if params != nil { + queryValues := queryURL.Query() + + if params.Limit != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "limit", runtime.ParamLocationQuery, *params.Limit); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + if params.Offset != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "offset", runtime.ParamLocationQuery, *params.Offset); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + queryURL.RawQuery = queryValues.Encode() + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewCreateCampaignRequest calls the generic CreateCampaign builder with application/json body +func NewCreateCampaignRequest(server string, projectID openapi_types.UUID, body CreateCampaignJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewCreateCampaignRequestWithBody(server, projectID, "application/json", bodyReader) +} + +// NewCreateCampaignRequestWithBody generates requests for CreateCampaign with any type of body +func NewCreateCampaignRequestWithBody(server string, projectID openapi_types.UUID, contentType string, body io.Reader) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "projectID", runtime.ParamLocationPath, projectID) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/api/admin/projects/%s/campaigns", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +// NewGetCampaignRequest generates requests for GetCampaign +func NewGetCampaignRequest(server string, projectID openapi_types.UUID, campaignID openapi_types.UUID) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "projectID", runtime.ParamLocationPath, projectID) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "campaignID", runtime.ParamLocationPath, campaignID) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/api/admin/projects/%s/campaigns/%s", pathParam0, pathParam1) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewUpdateCampaignRequest calls the generic UpdateCampaign builder with application/json body +func NewUpdateCampaignRequest(server string, projectID openapi_types.UUID, campaignID openapi_types.UUID, body UpdateCampaignJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewUpdateCampaignRequestWithBody(server, projectID, campaignID, "application/json", bodyReader) +} + +// NewUpdateCampaignRequestWithBody generates requests for UpdateCampaign with any type of body +func NewUpdateCampaignRequestWithBody(server string, projectID openapi_types.UUID, campaignID openapi_types.UUID, contentType string, body io.Reader) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "projectID", runtime.ParamLocationPath, projectID) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "campaignID", runtime.ParamLocationPath, campaignID) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/api/admin/projects/%s/campaigns/%s", pathParam0, pathParam1) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("PATCH", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +// NewGetTemplateRequest generates requests for GetTemplate +func NewGetTemplateRequest(server string, projectID openapi_types.UUID, templateID openapi_types.UUID) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "projectID", runtime.ParamLocationPath, projectID) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "templateID", runtime.ParamLocationPath, templateID) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/api/admin/projects/%s/templates/%s", pathParam0, pathParam1) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { + for _, r := range c.RequestEditors { + if err := r(ctx, req); err != nil { + return err + } + } + for _, r := range additionalEditors { + if err := r(ctx, req); err != nil { + return err + } + } + return nil +} + +// ClientWithResponses builds on ClientInterface to offer response payloads +type ClientWithResponses struct { + ClientInterface +} + +// NewClientWithResponses creates a new ClientWithResponses, which wraps +// Client with return type handling +func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) { + client, err := NewClient(server, opts...) + if err != nil { + return nil, err + } + return &ClientWithResponses{client}, nil +} + +// WithBaseURL overrides the baseURL. +func WithBaseURL(baseURL string) ClientOption { + return func(c *Client) error { + newBaseURL, err := url.Parse(baseURL) + if err != nil { + return err + } + c.Server = newBaseURL.String() + return nil + } +} + +// ClientWithResponsesInterface is the interface specification for the client with responses above. +type ClientWithResponsesInterface interface { + // ListCampaignsWithResponse request + ListCampaignsWithResponse(ctx context.Context, projectID openapi_types.UUID, params *ListCampaignsParams, reqEditors ...RequestEditorFn) (*ListCampaignsResponse, error) + + // CreateCampaignWithBodyWithResponse request with any body + CreateCampaignWithBodyWithResponse(ctx context.Context, projectID openapi_types.UUID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateCampaignResponse, error) + + CreateCampaignWithResponse(ctx context.Context, projectID openapi_types.UUID, body CreateCampaignJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateCampaignResponse, error) + + // GetCampaignWithResponse request + GetCampaignWithResponse(ctx context.Context, projectID openapi_types.UUID, campaignID openapi_types.UUID, reqEditors ...RequestEditorFn) (*GetCampaignResponse, error) + + // UpdateCampaignWithBodyWithResponse request with any body + UpdateCampaignWithBodyWithResponse(ctx context.Context, projectID openapi_types.UUID, campaignID openapi_types.UUID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateCampaignResponse, error) + + UpdateCampaignWithResponse(ctx context.Context, projectID openapi_types.UUID, campaignID openapi_types.UUID, body UpdateCampaignJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateCampaignResponse, error) + + // GetTemplateWithResponse request + GetTemplateWithResponse(ctx context.Context, projectID openapi_types.UUID, templateID openapi_types.UUID, reqEditors ...RequestEditorFn) (*GetTemplateResponse, error) +} + +type ListCampaignsResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *CampaignListResponse + JSONDefault *Error +} + +// Status returns HTTPResponse.Status +func (r ListCampaignsResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r ListCampaignsResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type CreateCampaignResponse struct { + Body []byte + HTTPResponse *http.Response + JSON201 *Campaign + JSONDefault *Error +} + +// Status returns HTTPResponse.Status +func (r CreateCampaignResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r CreateCampaignResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type GetCampaignResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *struct { + Data Campaign `json:"data"` + } + JSONDefault *Error +} + +// Status returns HTTPResponse.Status +func (r GetCampaignResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetCampaignResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type UpdateCampaignResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *Campaign + JSONDefault *Error +} + +// Status returns HTTPResponse.Status +func (r UpdateCampaignResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r UpdateCampaignResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type GetTemplateResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *Template + JSONDefault *Error +} + +// Status returns HTTPResponse.Status +func (r GetTemplateResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetTemplateResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// ListCampaignsWithResponse request returning *ListCampaignsResponse +func (c *ClientWithResponses) ListCampaignsWithResponse(ctx context.Context, projectID openapi_types.UUID, params *ListCampaignsParams, reqEditors ...RequestEditorFn) (*ListCampaignsResponse, error) { + rsp, err := c.ListCampaigns(ctx, projectID, params, reqEditors...) + if err != nil { + return nil, err + } + return ParseListCampaignsResponse(rsp) +} + +// CreateCampaignWithBodyWithResponse request with arbitrary body returning *CreateCampaignResponse +func (c *ClientWithResponses) CreateCampaignWithBodyWithResponse(ctx context.Context, projectID openapi_types.UUID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateCampaignResponse, error) { + rsp, err := c.CreateCampaignWithBody(ctx, projectID, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseCreateCampaignResponse(rsp) +} + +func (c *ClientWithResponses) CreateCampaignWithResponse(ctx context.Context, projectID openapi_types.UUID, body CreateCampaignJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateCampaignResponse, error) { + rsp, err := c.CreateCampaign(ctx, projectID, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseCreateCampaignResponse(rsp) +} + +// GetCampaignWithResponse request returning *GetCampaignResponse +func (c *ClientWithResponses) GetCampaignWithResponse(ctx context.Context, projectID openapi_types.UUID, campaignID openapi_types.UUID, reqEditors ...RequestEditorFn) (*GetCampaignResponse, error) { + rsp, err := c.GetCampaign(ctx, projectID, campaignID, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetCampaignResponse(rsp) +} + +// UpdateCampaignWithBodyWithResponse request with arbitrary body returning *UpdateCampaignResponse +func (c *ClientWithResponses) UpdateCampaignWithBodyWithResponse(ctx context.Context, projectID openapi_types.UUID, campaignID openapi_types.UUID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateCampaignResponse, error) { + rsp, err := c.UpdateCampaignWithBody(ctx, projectID, campaignID, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseUpdateCampaignResponse(rsp) +} + +func (c *ClientWithResponses) UpdateCampaignWithResponse(ctx context.Context, projectID openapi_types.UUID, campaignID openapi_types.UUID, body UpdateCampaignJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateCampaignResponse, error) { + rsp, err := c.UpdateCampaign(ctx, projectID, campaignID, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseUpdateCampaignResponse(rsp) +} + +// GetTemplateWithResponse request returning *GetTemplateResponse +func (c *ClientWithResponses) GetTemplateWithResponse(ctx context.Context, projectID openapi_types.UUID, templateID openapi_types.UUID, reqEditors ...RequestEditorFn) (*GetTemplateResponse, error) { + rsp, err := c.GetTemplate(ctx, projectID, templateID, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetTemplateResponse(rsp) +} + +// ParseListCampaignsResponse parses an HTTP response from a ListCampaignsWithResponse call +func ParseListCampaignsResponse(rsp *http.Response) (*ListCampaignsResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &ListCampaignsResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest CampaignListResponse + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && true: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSONDefault = &dest + + } + + return response, nil +} + +// ParseCreateCampaignResponse parses an HTTP response from a CreateCampaignWithResponse call +func ParseCreateCampaignResponse(rsp *http.Response) (*CreateCampaignResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &CreateCampaignResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: + var dest Campaign + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON201 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && true: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSONDefault = &dest + + } + + return response, nil +} + +// ParseGetCampaignResponse parses an HTTP response from a GetCampaignWithResponse call +func ParseGetCampaignResponse(rsp *http.Response) (*GetCampaignResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetCampaignResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest struct { + Data Campaign `json:"data"` + } + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && true: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSONDefault = &dest + + } + + return response, nil +} + +// ParseUpdateCampaignResponse parses an HTTP response from a UpdateCampaignWithResponse call +func ParseUpdateCampaignResponse(rsp *http.Response) (*UpdateCampaignResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &UpdateCampaignResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest Campaign + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && true: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSONDefault = &dest + + } + + return response, nil +} + +// ParseGetTemplateResponse parses an HTTP response from a GetTemplateWithResponse call +func ParseGetTemplateResponse(rsp *http.Response) (*GetTemplateResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetTemplateResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest Template + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && true: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSONDefault = &dest + + } + + return response, nil +} + +// ServerInterface represents all server handlers. +type ServerInterface interface { + // List campaigns + // (GET /api/admin/projects/{projectID}/campaigns) + ListCampaigns(w http.ResponseWriter, r *http.Request, projectID openapi_types.UUID, params ListCampaignsParams) + // Create campaign + // (POST /api/admin/projects/{projectID}/campaigns) + CreateCampaign(w http.ResponseWriter, r *http.Request, projectID openapi_types.UUID) + // Get campaign by ID + // (GET /api/admin/projects/{projectID}/campaigns/{campaignID}) + GetCampaign(w http.ResponseWriter, r *http.Request, projectID openapi_types.UUID, campaignID openapi_types.UUID) + // Update campaign + // (PATCH /api/admin/projects/{projectID}/campaigns/{campaignID}) + UpdateCampaign(w http.ResponseWriter, r *http.Request, projectID openapi_types.UUID, campaignID openapi_types.UUID) + // Get template by ID + // (GET /api/admin/projects/{projectID}/templates/{templateID}) + GetTemplate(w http.ResponseWriter, r *http.Request, projectID openapi_types.UUID, templateID openapi_types.UUID) +} + +// Unimplemented server implementation that returns http.StatusNotImplemented for each endpoint. + +type Unimplemented struct{} + +// List campaigns +// (GET /api/admin/projects/{projectID}/campaigns) +func (_ Unimplemented) ListCampaigns(w http.ResponseWriter, r *http.Request, projectID openapi_types.UUID, params ListCampaignsParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Create campaign +// (POST /api/admin/projects/{projectID}/campaigns) +func (_ Unimplemented) CreateCampaign(w http.ResponseWriter, r *http.Request, projectID openapi_types.UUID) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Get campaign by ID +// (GET /api/admin/projects/{projectID}/campaigns/{campaignID}) +func (_ Unimplemented) GetCampaign(w http.ResponseWriter, r *http.Request, projectID openapi_types.UUID, campaignID openapi_types.UUID) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Update campaign +// (PATCH /api/admin/projects/{projectID}/campaigns/{campaignID}) +func (_ Unimplemented) UpdateCampaign(w http.ResponseWriter, r *http.Request, projectID openapi_types.UUID, campaignID openapi_types.UUID) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Get template by ID +// (GET /api/admin/projects/{projectID}/templates/{templateID}) +func (_ Unimplemented) GetTemplate(w http.ResponseWriter, r *http.Request, projectID openapi_types.UUID, templateID openapi_types.UUID) { + w.WriteHeader(http.StatusNotImplemented) +} + +// ServerInterfaceWrapper converts contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface + HandlerMiddlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +type MiddlewareFunc func(http.Handler) http.Handler + +// ListCampaigns operation middleware +func (siw *ServerInterfaceWrapper) ListCampaigns(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "projectID" ------------- + var projectID openapi_types.UUID + + err = runtime.BindStyledParameterWithOptions("simple", "projectID", chi.URLParam(r, "projectID"), &projectID, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "projectID", Err: err}) + return + } + + ctx := r.Context() + + ctx = context.WithValue(ctx, HttpBearerAuthScopes, []string{}) + + r = r.WithContext(ctx) + + // Parameter object where we will unmarshal all parameters from the context + var params ListCampaignsParams + + // ------------- Optional query parameter "limit" ------------- + + err = runtime.BindQueryParameter("form", true, false, "limit", r.URL.Query(), ¶ms.Limit) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "limit", Err: err}) + return + } + + // ------------- Optional query parameter "offset" ------------- + + err = runtime.BindQueryParameter("form", true, false, "offset", r.URL.Query(), ¶ms.Offset) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "offset", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.ListCampaigns(w, r, projectID, params) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// CreateCampaign operation middleware +func (siw *ServerInterfaceWrapper) CreateCampaign(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "projectID" ------------- + var projectID openapi_types.UUID + + err = runtime.BindStyledParameterWithOptions("simple", "projectID", chi.URLParam(r, "projectID"), &projectID, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "projectID", Err: err}) + return + } + + ctx := r.Context() + + ctx = context.WithValue(ctx, HttpBearerAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.CreateCampaign(w, r, projectID) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// GetCampaign operation middleware +func (siw *ServerInterfaceWrapper) GetCampaign(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "projectID" ------------- + var projectID openapi_types.UUID + + err = runtime.BindStyledParameterWithOptions("simple", "projectID", chi.URLParam(r, "projectID"), &projectID, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "projectID", Err: err}) + return + } + + // ------------- Path parameter "campaignID" ------------- + var campaignID openapi_types.UUID + + err = runtime.BindStyledParameterWithOptions("simple", "campaignID", chi.URLParam(r, "campaignID"), &campaignID, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "campaignID", Err: err}) + return + } + + ctx := r.Context() + + ctx = context.WithValue(ctx, HttpBearerAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetCampaign(w, r, projectID, campaignID) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// UpdateCampaign operation middleware +func (siw *ServerInterfaceWrapper) UpdateCampaign(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "projectID" ------------- + var projectID openapi_types.UUID + + err = runtime.BindStyledParameterWithOptions("simple", "projectID", chi.URLParam(r, "projectID"), &projectID, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "projectID", Err: err}) + return + } + + // ------------- Path parameter "campaignID" ------------- + var campaignID openapi_types.UUID + + err = runtime.BindStyledParameterWithOptions("simple", "campaignID", chi.URLParam(r, "campaignID"), &campaignID, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "campaignID", Err: err}) + return + } + + ctx := r.Context() + + ctx = context.WithValue(ctx, HttpBearerAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.UpdateCampaign(w, r, projectID, campaignID) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// GetTemplate operation middleware +func (siw *ServerInterfaceWrapper) GetTemplate(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "projectID" ------------- + var projectID openapi_types.UUID + + err = runtime.BindStyledParameterWithOptions("simple", "projectID", chi.URLParam(r, "projectID"), &projectID, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "projectID", Err: err}) + return + } + + // ------------- Path parameter "templateID" ------------- + var templateID openapi_types.UUID + + err = runtime.BindStyledParameterWithOptions("simple", "templateID", chi.URLParam(r, "templateID"), &templateID, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "templateID", Err: err}) + return + } + + ctx := r.Context() + + ctx = context.WithValue(ctx, HttpBearerAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetTemplate(w, r, projectID, templateID) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +type UnescapedCookieParamError struct { + ParamName string + Err error +} + +func (e *UnescapedCookieParamError) Error() string { + return fmt.Sprintf("error unescaping cookie parameter '%s'", e.ParamName) +} + +func (e *UnescapedCookieParamError) Unwrap() error { + return e.Err +} + +type UnmarshalingParamError struct { + ParamName string + Err error +} + +func (e *UnmarshalingParamError) Error() string { + return fmt.Sprintf("Error unmarshaling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) +} + +func (e *UnmarshalingParamError) Unwrap() error { + return e.Err +} + +type RequiredParamError struct { + ParamName string +} + +func (e *RequiredParamError) Error() string { + return fmt.Sprintf("Query argument %s is required, but not found", e.ParamName) +} + +type RequiredHeaderError struct { + ParamName string + Err error +} + +func (e *RequiredHeaderError) Error() string { + return fmt.Sprintf("Header parameter %s is required, but not found", e.ParamName) +} + +func (e *RequiredHeaderError) Unwrap() error { + return e.Err +} + +type InvalidParamFormatError struct { + ParamName string + Err error +} + +func (e *InvalidParamFormatError) Error() string { + return fmt.Sprintf("Invalid format for parameter %s: %s", e.ParamName, e.Err.Error()) +} + +func (e *InvalidParamFormatError) Unwrap() error { + return e.Err +} + +type TooManyValuesForParamError struct { + ParamName string + Count int +} + +func (e *TooManyValuesForParamError) Error() string { + return fmt.Sprintf("Expected one value for %s, got %d", e.ParamName, e.Count) +} + +// Handler creates http.Handler with routing matching OpenAPI spec. +func Handler(si ServerInterface) http.Handler { + return HandlerWithOptions(si, ChiServerOptions{}) +} + +type ChiServerOptions struct { + BaseURL string + BaseRouter chi.Router + Middlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +// HandlerFromMux creates http.Handler with routing matching OpenAPI spec based on the provided mux. +func HandlerFromMux(si ServerInterface, r chi.Router) http.Handler { + return HandlerWithOptions(si, ChiServerOptions{ + BaseRouter: r, + }) +} + +func HandlerFromMuxWithBaseURL(si ServerInterface, r chi.Router, baseURL string) http.Handler { + return HandlerWithOptions(si, ChiServerOptions{ + BaseURL: baseURL, + BaseRouter: r, + }) +} + +// HandlerWithOptions creates http.Handler with additional options +func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handler { + r := options.BaseRouter + + if r == nil { + r = chi.NewRouter() + } + if options.ErrorHandlerFunc == nil { + options.ErrorHandlerFunc = func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, err.Error(), http.StatusBadRequest) + } + } + wrapper := ServerInterfaceWrapper{ + Handler: si, + HandlerMiddlewares: options.Middlewares, + ErrorHandlerFunc: options.ErrorHandlerFunc, + } + + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/api/admin/projects/{projectID}/campaigns", wrapper.ListCampaigns) + }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/api/admin/projects/{projectID}/campaigns", wrapper.CreateCampaign) + }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/api/admin/projects/{projectID}/campaigns/{campaignID}", wrapper.GetCampaign) + }) + r.Group(func(r chi.Router) { + r.Patch(options.BaseURL+"/api/admin/projects/{projectID}/campaigns/{campaignID}", wrapper.UpdateCampaign) + }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/api/admin/projects/{projectID}/templates/{templateID}", wrapper.GetTemplate) + }) + + return r +} diff --git a/services/platform/db/migration.stub b/services/platform/db/migration.stub deleted file mode 100644 index 5f8f06c4..00000000 --- a/services/platform/db/migration.stub +++ /dev/null @@ -1,7 +0,0 @@ -exports.up = async function(knex) { - -} - -exports.down = async function(knex) { - -} diff --git a/services/platform/db/migrations/1760123845_postgresql_migration.js b/services/platform/db/migrations/1760123845_postgresql_migration.js deleted file mode 100644 index 75af1177..00000000 --- a/services/platform/db/migrations/1760123845_postgresql_migration.js +++ /dev/null @@ -1,981 +0,0 @@ -exports.up = async function (knex) { - await knex.raw(` -CREATE OR REPLACE FUNCTION set_updated_at () RETURNS trigger LANGUAGE plpgsql AS $$ -BEGIN - NEW.updated_at := current_timestamp; - RETURN NEW; -END; $$; - -CREATE OR REPLACE FUNCTION increment_version () RETURNS trigger LANGUAGE plpgsql AS $$ -BEGIN - NEW.version := OLD.version + 1; - RETURN NEW; -END; $$; - -CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; - -/* -------------------- admins -------------------- */ -CREATE TABLE admins ( - id uuid PRIMARY KEY DEFAULT uuid_generate_v4 (), - organization_id uuid, - first_name VARCHAR(255), - last_name VARCHAR(255), - email VARCHAR(255) NOT NULL, - image_url VARCHAR(255), - created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, - deleted_at TIMESTAMPTZ, - role VARCHAR(64) NOT NULL DEFAULT 'member' -); - -CREATE TRIGGER admins_set_updated_at BEFORE -UPDATE ON admins FOR EACH ROW -EXECUTE FUNCTION set_updated_at (); - --- Indexes -CREATE INDEX admins_organization_id_idx ON admins (organization_id); - -/* -------------------- access_tokens -------------------- */ -CREATE TABLE access_tokens ( - id uuid PRIMARY KEY DEFAULT uuid_generate_v4 (), - admin_id uuid NOT NULL, - token TEXT NOT NULL, - revoked BOOLEAN DEFAULT FALSE, - expires_at TIMESTAMPTZ, - created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, - ip VARCHAR(255), - user_agent VARCHAR(255) -); - -CREATE TRIGGER access_tokens_set_updated_at BEFORE -UPDATE ON access_tokens FOR EACH ROW -EXECUTE FUNCTION set_updated_at (); - --- Indexes -CREATE INDEX access_tokens_token_idx ON access_tokens (token); - -CREATE INDEX access_tokens_admin_id_idx ON access_tokens (admin_id); - -/* -------------------- audits -------------------- */ -CREATE TABLE audits ( - id uuid PRIMARY KEY DEFAULT uuid_generate_v4 (), - project_id uuid NOT NULL, - admin_id uuid, - item_type VARCHAR(50) NOT NULL, - item_id uuid NOT NULL, - event VARCHAR(50) NOT NULL, - object JSONB, - object_changes JSONB, - created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP -); - -CREATE TRIGGER audits_set_updated_at BEFORE -UPDATE ON audits FOR EACH ROW -EXECUTE FUNCTION set_updated_at (); - --- Indexes -CREATE INDEX audits_project_id_idx ON audits (project_id); - -CREATE INDEX audits_admin_id_idx ON audits (admin_id); - -CREATE INDEX audits_event_idx ON audits (event); - -CREATE INDEX audits_item_type_item_id_idx ON audits (item_type, item_id); - -CREATE INDEX audits_created_at_idx ON audits (created_at); - -/* -------------------- job_locks -------------------- */ -CREATE TABLE job_locks ( - id uuid PRIMARY KEY DEFAULT uuid_generate_v4 (), - key VARCHAR(255), - owner VARCHAR(255), - expiration TIMESTAMPTZ, - created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP -); - -CREATE TRIGGER job_locks_set_updated_at BEFORE -UPDATE ON job_locks FOR EACH ROW -EXECUTE FUNCTION set_updated_at (); - --- Indexes -CREATE UNIQUE INDEX job_locks_key_uniq ON job_locks (key); - -/* -------------------- campaigns -------------------- */ -CREATE TABLE campaigns ( - id uuid PRIMARY KEY DEFAULT uuid_generate_v4 (), - type VARCHAR(255), - project_id uuid NOT NULL, - list_ids JSONB, - exclusion_list_ids JSONB, - name VARCHAR(255) DEFAULT '', - channel VARCHAR(255) NOT NULL, - provider_id uuid NOT NULL, - subscription_id uuid NOT NULL, - state VARCHAR(20), - delivery JSONB, - send_at TIMESTAMPTZ, - send_in_user_timezone BOOLEAN DEFAULT FALSE, - created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, - deleted_at TIMESTAMPTZ -); - -CREATE TRIGGER campaigns_set_updated_at BEFORE -UPDATE ON campaigns FOR EACH ROW -EXECUTE FUNCTION set_updated_at (); - --- Indexes -CREATE INDEX campaigns_project_id_idx ON campaigns (project_id); - -CREATE INDEX campaigns_provider_id_idx ON campaigns (provider_id); - -CREATE INDEX campaigns_subscription_id_idx ON campaigns (subscription_id); - -CREATE INDEX campaigns_send_at_idx ON campaigns (send_at); - -CREATE INDEX campaigns_project_state_idx ON campaigns (project_id, state); - -/* -------------------- campaign_sends -------------------- */ -CREATE TABLE campaign_sends ( - id uuid DEFAULT uuid_generate_v4 (), - campaign_id uuid NOT NULL, - user_id uuid NOT NULL, - state VARCHAR(50), - send_at TIMESTAMPTZ, - opened_at TIMESTAMPTZ, - clicks int4 DEFAULT 0, - created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, - reference_type VARCHAR(255), - reference_id VARCHAR(255) NOT NULL DEFAULT '0', - PRIMARY KEY (campaign_id, user_id, reference_id) -); - -CREATE TRIGGER campaign_sends_set_updated_at BEFORE -UPDATE ON campaign_sends FOR EACH ROW -EXECUTE FUNCTION set_updated_at (); - --- Indexes -CREATE INDEX campaign_sends_user_id_idx ON campaign_sends (user_id); - -CREATE INDEX campaign_sends_send_at_idx ON campaign_sends (send_at); - -CREATE INDEX campaign_sends_state_idx ON campaign_sends (state); - -CREATE INDEX campaign_sends_campaign_state_idx ON campaign_sends (campaign_id, state); - -/* -------------------- devices -------------------- */ -CREATE TABLE devices ( - id uuid PRIMARY KEY DEFAULT uuid_generate_v4 (), - project_id uuid NOT NULL, - user_id uuid NOT NULL, - device_id TEXT NOT NULL, - token VARCHAR(255), - os VARCHAR(255), - os_version VARCHAR(255), - model VARCHAR(255), - app_version VARCHAR(255), - app_build VARCHAR(255), - created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP -); - -CREATE TRIGGER devices_set_updated_at BEFORE -UPDATE ON devices FOR EACH ROW -EXECUTE FUNCTION set_updated_at (); - --- Indexes -CREATE UNIQUE INDEX devices_project_device_uniq ON devices (project_id, device_id); - -CREATE UNIQUE INDEX devices_project_token_uniq ON devices (project_id, token); - -CREATE INDEX devices_user_id_idx ON devices (user_id); - -/* -------------------- entity_tags -------------------- */ -CREATE TABLE entity_tags ( - id uuid PRIMARY KEY DEFAULT uuid_generate_v4 (), - tag_id uuid NOT NULL, - entity VARCHAR(255), - entity_id uuid NOT NULL, - created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP -); - -CREATE TRIGGER entity_tags_set_updated_at BEFORE -UPDATE ON entity_tags FOR EACH ROW -EXECUTE FUNCTION set_updated_at (); - --- Indexes -CREATE INDEX entity_tags_tag_id_idx ON entity_tags (tag_id); - -CREATE INDEX entity_tags_entity_entity_id_idx ON entity_tags (entity, entity_id); - -/* -------------------- images -------------------- */ -CREATE TABLE images ( - id uuid PRIMARY KEY DEFAULT uuid_generate_v4 (), - project_id uuid NOT NULL, - uuid VARCHAR(255) NOT NULL, - name VARCHAR(255) DEFAULT '', - original_name VARCHAR(255), - extension VARCHAR(255), - alt VARCHAR(255), - file_size int4, - created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP -); - -CREATE TRIGGER images_set_updated_at BEFORE -UPDATE ON images FOR EACH ROW -EXECUTE FUNCTION set_updated_at (); - --- Indexes -CREATE INDEX images_project_id_idx ON images (project_id); - -CREATE UNIQUE INDEX images_project_uuid_uniq ON images (project_id, uuid); - -/* -------------------- journey_steps -------------------- */ -CREATE TABLE journey_steps ( - id uuid PRIMARY KEY DEFAULT uuid_generate_v4 (), - type VARCHAR(255) DEFAULT '', - journey_id uuid, - child_id uuid, - data JSONB, - x float8 NOT NULL DEFAULT 0, - y float8 NOT NULL DEFAULT 0, - created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, - external_id VARCHAR(128), - data_key VARCHAR(255), - stats JSONB, - stats_at TIMESTAMPTZ, - name VARCHAR(128), - next_scheduled_at TIMESTAMPTZ -); - -CREATE TRIGGER journey_steps_set_updated_at BEFORE -UPDATE ON journey_steps FOR EACH ROW -EXECUTE FUNCTION set_updated_at (); - --- Indexes -CREATE INDEX journey_steps_child_id_idx ON journey_steps (child_id); - -CREATE UNIQUE INDEX journey_steps_journey_external_uniq ON journey_steps (journey_id, external_id); - -/* -------------------- journey_step_child -------------------- */ -CREATE TABLE journey_step_child ( - id uuid PRIMARY KEY DEFAULT uuid_generate_v4 (), - step_id uuid NOT NULL, - child_id uuid NOT NULL, - data JSONB, - created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, - priority int4 NOT NULL DEFAULT 0, - path VARCHAR(128) -); - -CREATE TRIGGER journey_step_child_set_updated_at BEFORE -UPDATE ON journey_step_child FOR EACH ROW -EXECUTE FUNCTION set_updated_at (); - --- Indexes -CREATE INDEX journey_step_child_child_id_idx ON journey_step_child (child_id); - -CREATE UNIQUE INDEX journey_step_child_step_child_uniq ON journey_step_child (step_id, child_id); - -/* -------------------- journey_user_step -------------------- */ -CREATE TABLE journey_user_step ( - id uuid PRIMARY KEY DEFAULT uuid_generate_v4 (), - user_id uuid, - journey_id uuid, - step_id uuid, - type VARCHAR(255), - created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, - delay_until TIMESTAMPTZ, - entrance_id uuid, - ended_at TIMESTAMPTZ, - data JSONB, - ref VARCHAR(64) -); - -CREATE TRIGGER journey_user_step_set_updated_at BEFORE -UPDATE ON journey_user_step FOR EACH ROW -EXECUTE FUNCTION set_updated_at (); - --- Indexes -CREATE INDEX journey_user_step_step_id_idx ON journey_user_step (step_id); - -CREATE INDEX journey_user_step_entrance_id_idx ON journey_user_step (entrance_id); - -CREATE INDEX journey_user_step_user_id_idx ON journey_user_step (user_id); - -CREATE INDEX journey_user_step_journey_type_delay_idx ON journey_user_step (journey_id, type, delay_until); - -CREATE INDEX journey_user_step_ref_idx ON journey_user_step (ref); - --- Helpful extras: -CREATE INDEX journey_user_step_delay_until_notnull_idx ON journey_user_step (delay_until) -WHERE - delay_until IS NOT NULL; - -CREATE INDEX journey_user_step_user_journey_idx ON journey_user_step (user_id, journey_id); - -/* -------------------- organizations -------------------- */ -CREATE TABLE organizations ( - id uuid PRIMARY KEY DEFAULT uuid_generate_v4 (), - username VARCHAR(255), - domain VARCHAR(255), - auth JSONB, - created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, - tracking_deeplink_mirror_url VARCHAR(255), - notification_provider_id uuid -); - -CREATE TRIGGER organizations_set_updated_at BEFORE -UPDATE ON organizations FOR EACH ROW -EXECUTE FUNCTION set_updated_at (); - --- Indexes -CREATE INDEX organizations_domain_idx ON organizations (domain); - -CREATE UNIQUE INDEX organizations_username_uniq ON organizations (username); - -CREATE INDEX organizations_notification_provider_id_idx ON organizations (notification_provider_id); - -/* -------------------- projects -------------------- */ -CREATE TABLE projects ( - id uuid PRIMARY KEY DEFAULT uuid_generate_v4 (), - organization_id uuid, - name VARCHAR(255) DEFAULT '', - description VARCHAR(2048) DEFAULT '', - created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, - deleted_at TIMESTAMPTZ, - locale VARCHAR(50), - timezone VARCHAR(50), - text_opt_out_message VARCHAR(255), - link_wrap_email BOOLEAN DEFAULT FALSE, - text_help_message VARCHAR(255), - link_wrap_push BOOLEAN DEFAULT FALSE -); - -CREATE TRIGGER projects_set_updated_at BEFORE -UPDATE ON projects FOR EACH ROW -EXECUTE FUNCTION set_updated_at (); - --- Indexes -CREATE INDEX projects_organization_id_idx ON projects (organization_id); - -CREATE INDEX projects_name_idx ON projects (name); - -/* -------------------- journeys -------------------- */ -CREATE TABLE journeys ( - id uuid PRIMARY KEY DEFAULT uuid_generate_v4 (), - project_id uuid NOT NULL, - name VARCHAR(255) DEFAULT '', - description VARCHAR(2048) DEFAULT '', - created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, - deleted_at TIMESTAMPTZ, - stats JSONB, - stats_at TIMESTAMPTZ, - parent_id uuid, - status VARCHAR(255) -); - -CREATE TRIGGER journeys_set_updated_at BEFORE -UPDATE ON journeys FOR EACH ROW -EXECUTE FUNCTION set_updated_at (); - --- Indexes -CREATE INDEX journeys_project_id_idx ON journeys (project_id); - -CREATE INDEX journeys_parent_id_idx ON journeys (parent_id); - -/* -------------------- lists -------------------- */ -CREATE TABLE lists ( - id uuid PRIMARY KEY DEFAULT uuid_generate_v4 (), - project_id uuid NOT NULL, - name VARCHAR(255) DEFAULT '', - type VARCHAR(25), - state VARCHAR(25), - rule JSONB, - rule_id uuid, - users_count int4, - version int4 NOT NULL DEFAULT 0, - created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, - deleted_at TIMESTAMPTZ, - is_visible BOOLEAN DEFAULT TRUE, - refreshed_at TIMESTAMPTZ -); - -CREATE TRIGGER lists_set_updated_at BEFORE -UPDATE ON lists FOR EACH ROW -EXECUTE FUNCTION set_updated_at (); - -CREATE TRIGGER list_increment_version BEFORE -UPDATE ON lists FOR EACH ROW -EXECUTE FUNCTION increment_version (); - --- Indexes -CREATE INDEX lists_project_id_idx ON lists (project_id); - -CREATE INDEX lists_rule_id_idx ON lists (rule_id); - -CREATE INDEX lists_project_active_idx ON lists (project_id) -WHERE - deleted_at IS NULL; - -/* -------------------- locales -------------------- */ -CREATE TABLE locales ( - id uuid PRIMARY KEY DEFAULT uuid_generate_v4 (), - project_id uuid NOT NULL, - key VARCHAR(255), - label VARCHAR(255), - created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP -); - -CREATE TRIGGER locales_set_updated_at BEFORE -UPDATE ON locales FOR EACH ROW -EXECUTE FUNCTION set_updated_at (); - --- Indexes -CREATE INDEX locales_project_id_idx ON locales (project_id); - -/* -------------------- notifications -------------------- */ -CREATE TABLE notifications ( - id uuid PRIMARY KEY DEFAULT uuid_generate_v4 (), - project_id uuid NOT NULL, - user_id uuid NOT NULL, - content_type VARCHAR(255), - content JSONB, - read_at TIMESTAMPTZ, - expires_at TIMESTAMPTZ, - created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP -); - -CREATE TRIGGER notifications_set_updated_at BEFORE -UPDATE ON notifications FOR EACH ROW -EXECUTE FUNCTION set_updated_at (); - --- Indexes -CREATE INDEX notifications_user_id_idx ON notifications (user_id); - -CREATE INDEX notifications_project_id_idx ON notifications (project_id); - -CREATE INDEX notifications_read_at_idx ON notifications (read_at); - -CREATE INDEX notifications_expires_at_idx ON notifications (expires_at); - -CREATE INDEX notifications_user_unread_idx ON notifications (user_id) -WHERE - read_at IS NULL; - -/* -------------------- project_admins -------------------- */ -CREATE TABLE project_admins ( - id uuid PRIMARY KEY DEFAULT uuid_generate_v4 (), - project_id uuid NOT NULL, - admin_id uuid NOT NULL, - created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, - deleted_at TIMESTAMPTZ, - role VARCHAR(64) NOT NULL DEFAULT 'support' -); - -CREATE TRIGGER project_admins_set_updated_at BEFORE -UPDATE ON project_admins FOR EACH ROW -EXECUTE FUNCTION set_updated_at (); - --- Indexes -CREATE INDEX project_admins_project_id_idx ON project_admins (project_id); - -CREATE INDEX project_admins_admin_id_idx ON project_admins (admin_id); - -/* -------------------- project_api_keys -------------------- */ -CREATE TABLE project_api_keys ( - id uuid PRIMARY KEY DEFAULT uuid_generate_v4 (), - project_id uuid NOT NULL, - value VARCHAR(255) NOT NULL, - scope VARCHAR(20), - name VARCHAR(255) NOT NULL, - description VARCHAR(2048), - created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, - deleted_at TIMESTAMPTZ, - role VARCHAR(64) NOT NULL DEFAULT 'support' -); - -CREATE TRIGGER project_api_keys_set_updated_at BEFORE -UPDATE ON project_api_keys FOR EACH ROW -EXECUTE FUNCTION set_updated_at (); - --- Indexes -CREATE UNIQUE INDEX project_api_keys_value_uniq ON project_api_keys (value); - -CREATE INDEX project_api_keys_project_id_idx ON project_api_keys (project_id); - -/* -------------------- users -------------------- */ -CREATE TABLE users ( - id uuid PRIMARY KEY DEFAULT uuid_generate_v4 (), - project_id uuid NOT NULL, - anonymous_id VARCHAR(255), - external_id VARCHAR(255), - email VARCHAR(255), - phone VARCHAR(64), - data JSONB, - devices JSONB, - timezone VARCHAR(50), - locale VARCHAR(255), - created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, - unsubscribe_ids UUID[] NOT NULL DEFAULT '{}', - version int4 NOT NULL DEFAULT 0, - has_push_device BOOLEAN DEFAULT FALSE -); - -CREATE TRIGGER users_set_updated_at BEFORE -UPDATE ON users FOR EACH ROW -EXECUTE FUNCTION set_updated_at (); - -CREATE TRIGGER users_increment_version BEFORE -UPDATE ON users FOR EACH ROW -EXECUTE FUNCTION increment_version (); - --- Indexes -CREATE UNIQUE INDEX users_project_anonymous_uniq ON users (project_id, anonymous_id); - -CREATE UNIQUE INDEX users_project_external_uniq ON users (project_id, external_id); - -CREATE INDEX users_email_idx ON users (email); - -CREATE INDEX users_phone_idx ON users (phone); - -CREATE INDEX users_external_id_idx ON users (external_id); - -CREATE INDEX users_updated_at_idx ON users (updated_at); - -CREATE INDEX users_anonymous_id_idx ON users (anonymous_id); - -CREATE INDEX users_data_idx ON users USING GIN (data); - -/* If you always scope by project, the single-column indexes above can be dropped in favor of composites. */ -/* -------------------- providers -------------------- */ -CREATE TABLE providers ( - id uuid PRIMARY KEY DEFAULT uuid_generate_v4 (), - project_id uuid NOT NULL, - type VARCHAR(255) DEFAULT '', - "group" VARCHAR(255) NOT NULL, - data JSONB, - is_default BOOLEAN DEFAULT FALSE, - rate_limit int4, - rate_interval VARCHAR(12) DEFAULT 'second', - created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, - name VARCHAR(255), - deleted_at TIMESTAMPTZ -); - -CREATE TRIGGER providers_set_updated_at BEFORE -UPDATE ON providers FOR EACH ROW -EXECUTE FUNCTION set_updated_at (); - --- Indexes -CREATE INDEX providers_project_id_idx ON providers (project_id); - -/* -------------------- subscriptions -------------------- */ -CREATE TABLE subscriptions ( - id uuid PRIMARY KEY DEFAULT uuid_generate_v4 (), - project_id uuid NOT NULL, - name VARCHAR(255) DEFAULT '', - channel VARCHAR(255) NOT NULL, - created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, - is_public BOOLEAN NOT NULL DEFAULT TRUE -); - -CREATE TRIGGER subscriptions_set_updated_at BEFORE -UPDATE ON subscriptions FOR EACH ROW -EXECUTE FUNCTION set_updated_at (); - --- Indexes -CREATE INDEX subscriptions_project_id_idx ON subscriptions (project_id); - -/* -------------------- tags -------------------- */ -CREATE TABLE tags ( - id uuid PRIMARY KEY DEFAULT uuid_generate_v4 (), - project_id uuid NOT NULL, - name VARCHAR(255) DEFAULT '', - created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP -); - -CREATE TRIGGER tags_set_updated_at BEFORE -UPDATE ON tags FOR EACH ROW -EXECUTE FUNCTION set_updated_at (); - --- Indexes -CREATE INDEX tags_project_id_idx ON tags (project_id); - -/* -------------------- rules -------------------- */ -CREATE TABLE rules ( - id uuid PRIMARY KEY DEFAULT uuid_generate_v4 (), - project_id uuid NOT NULL, - uuid VARCHAR(255), - root_uuid VARCHAR(255), - parent_uuid VARCHAR(255), - type VARCHAR(255), - "group" VARCHAR(255), - path VARCHAR(255), - operator VARCHAR(255), - value VARCHAR(255), - created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, - UNIQUE (uuid) -); - -CREATE TRIGGER rules_set_updated_at BEFORE -UPDATE ON rules FOR EACH ROW -EXECUTE FUNCTION set_updated_at (); - --- Indexes -CREATE INDEX rules_project_id_idx ON rules (project_id); - -CREATE INDEX rules_parent_uuid_idx ON rules (parent_uuid); - -CREATE INDEX rules_root_uuid_idx ON rules (root_uuid); - -CREATE INDEX rules_group_idx ON rules ("group"); - -CREATE INDEX rules_type_idx ON rules (type); - -CREATE INDEX rules_value_idx ON rules (value); - -/* -------------------- project_rule_paths -------------------- */ -CREATE SEQUENCE IF NOT EXISTS project_rule_paths_id_seq; - -DROP TYPE IF EXISTS project_rule_paths_visibility; - -CREATE TYPE project_rule_paths_visibility AS ENUM('public', 'hidden', 'classified'); - -CREATE TABLE project_rule_paths ( - id uuid PRIMARY KEY DEFAULT uuid_generate_v4 (), - project_id uuid NOT NULL, - path VARCHAR(255) NOT NULL, - name VARCHAR(255), - type VARCHAR(50) NOT NULL, - created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, - data_type VARCHAR(255) DEFAULT 'string', - visibility project_rule_paths_visibility NOT NULL DEFAULT 'public' -); - -CREATE TRIGGER project_rule_paths_set_updated_at BEFORE -UPDATE ON project_rule_paths FOR EACH ROW -EXECUTE FUNCTION set_updated_at (); - --- Indexes -CREATE INDEX project_rule_paths_project_id_idx ON project_rule_paths (project_id); - -/* -------------------- resources -------------------- */ -CREATE TABLE resources ( - id uuid PRIMARY KEY DEFAULT uuid_generate_v4 (), - project_id uuid NOT NULL, - type VARCHAR(255), - name VARCHAR(255), - value JSONB, - created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP -); - -CREATE TRIGGER resources_set_updated_at BEFORE -UPDATE ON resources FOR EACH ROW -EXECUTE FUNCTION set_updated_at (); - --- Indexes -CREATE INDEX resources_project_id_idx ON resources (project_id); - -/* -------------------- rule_evaluations -------------------- */ -CREATE TABLE rule_evaluations ( - id uuid PRIMARY KEY DEFAULT uuid_generate_v4 (), - rule_id uuid NOT NULL, - user_id uuid NOT NULL, - result BOOLEAN, - created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP -); - -CREATE TRIGGER rule_evaluations_set_updated_at BEFORE -UPDATE ON rule_evaluations FOR EACH ROW -EXECUTE FUNCTION set_updated_at (); - --- Indexes -CREATE INDEX rule_evaluations_rule_id_idx ON rule_evaluations (rule_id); - -CREATE UNIQUE INDEX rule_evaluations_user_rule_uniq ON rule_evaluations (user_id, rule_id); - -/* -------------------- templates -------------------- */ -CREATE TABLE templates ( - id uuid PRIMARY KEY DEFAULT uuid_generate_v4 (), - project_id uuid NOT NULL, - name VARCHAR(255), - type VARCHAR(50), - data JSONB, - created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, - locale VARCHAR(50), - campaign_id uuid NOT NULL -); - -CREATE TRIGGER templates_set_updated_at BEFORE -UPDATE ON templates FOR EACH ROW -EXECUTE FUNCTION set_updated_at (); - --- Indexes -CREATE INDEX templates_campaign_id_idx ON templates (campaign_id); - -CREATE INDEX templates_project_id_idx ON templates (project_id); - -/* -------------------- user_events -------------------- */ -CREATE TABLE user_events ( - id uuid PRIMARY KEY DEFAULT uuid_generate_v4 (), - name VARCHAR(255) DEFAULT '', - project_id uuid NOT NULL, - user_id uuid NOT NULL, - data JSONB, - created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP -); - -CREATE TRIGGER user_events_set_updated_at BEFORE -UPDATE ON user_events FOR EACH ROW -EXECUTE FUNCTION set_updated_at (); - --- Indexes -CREATE INDEX user_events_project_id_idx ON user_events (project_id); - -CREATE INDEX user_events_user_id_idx ON user_events (user_id); - -CREATE INDEX user_events_created_at_idx ON user_events (created_at); - -CREATE INDEX user_events_name_user_idx ON user_events (name, user_id); - -CREATE INDEX user_events_data_idx ON user_events USING GIN (data); - -/* -------------------- user_list -------------------- */ -CREATE TABLE user_list ( - id uuid PRIMARY KEY DEFAULT uuid_generate_v4 (), - user_id uuid NOT NULL, - list_id uuid NOT NULL, - event_id uuid, - version int4 NOT NULL DEFAULT 0, - created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, - deleted_at TIMESTAMPTZ -); - -CREATE TRIGGER user_list_set_updated_at BEFORE -UPDATE ON user_list FOR EACH ROW -EXECUTE FUNCTION set_updated_at (); - -CREATE TRIGGER user_list_increment_version BEFORE -UPDATE ON user_list FOR EACH ROW -EXECUTE FUNCTION increment_version (); - --- Indexes -CREATE UNIQUE INDEX user_list_user_list_uniq ON user_list (user_id, list_id); - -CREATE INDEX user_list_version_idx ON user_list (version); - -CREATE INDEX user_list_list_id_idx ON user_list (list_id); - -CREATE INDEX user_list_event_id_idx ON user_list (event_id); - -CREATE INDEX user_list_created_at_idx ON user_list (created_at); - -/* -------------------- user_subscription -------------------- */ -CREATE TABLE user_subscription ( - id uuid PRIMARY KEY DEFAULT uuid_generate_v4 (), - subscription_id uuid NOT NULL, - user_id uuid NOT NULL, - state int2, - created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP -); - -CREATE TRIGGER user_subscription_set_updated_at BEFORE -UPDATE ON user_subscription FOR EACH ROW -EXECUTE FUNCTION set_updated_at (); - --- Indexes -CREATE INDEX user_subscription_user_id_idx ON user_subscription (user_id); - -CREATE INDEX user_subscription_subscription_id_idx ON user_subscription (subscription_id); - -/* -------------------- Foreign keys (unchanged order) -------------------- */ -ALTER TABLE admins -ADD FOREIGN KEY (organization_id) REFERENCES organizations (id) ON DELETE CASCADE; - -ALTER TABLE access_tokens -ADD FOREIGN KEY (admin_id) REFERENCES admins (id) ON DELETE CASCADE; - -ALTER TABLE audits -ADD FOREIGN KEY (admin_id) REFERENCES admins (id) ON DELETE CASCADE; - -ALTER TABLE audits -ADD FOREIGN KEY (project_id) REFERENCES projects (id) ON DELETE CASCADE; - -ALTER TABLE campaigns -ADD FOREIGN KEY (provider_id) REFERENCES providers (id) ON DELETE CASCADE; - -ALTER TABLE campaigns -ADD FOREIGN KEY (subscription_id) REFERENCES subscriptions (id) ON DELETE CASCADE; - -ALTER TABLE campaigns -ADD FOREIGN KEY (project_id) REFERENCES projects (id) ON DELETE CASCADE; - -ALTER TABLE campaign_sends -ADD FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE; - -ALTER TABLE campaign_sends -ADD FOREIGN KEY (campaign_id) REFERENCES campaigns (id) ON DELETE CASCADE; - -ALTER TABLE entity_tags -ADD FOREIGN KEY (tag_id) REFERENCES tags (id) ON DELETE CASCADE; - -ALTER TABLE devices -ADD FOREIGN KEY (project_id) REFERENCES projects (id) ON DELETE CASCADE; - -ALTER TABLE devices -ADD FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE; - -ALTER TABLE images -ADD FOREIGN KEY (project_id) REFERENCES projects (id) ON DELETE CASCADE; - -ALTER TABLE journey_step_child -ADD FOREIGN KEY (child_id) REFERENCES journey_steps (id) ON DELETE CASCADE; - -ALTER TABLE journey_step_child -ADD FOREIGN KEY (step_id) REFERENCES journey_steps (id) ON DELETE CASCADE; - -ALTER TABLE user_subscription -ADD FOREIGN KEY (subscription_id) REFERENCES subscriptions (id) ON DELETE CASCADE; - -ALTER TABLE user_subscription -ADD FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE; - -ALTER TABLE user_list -ADD FOREIGN KEY (list_id) REFERENCES lists (id) ON DELETE CASCADE; - -ALTER TABLE user_list -ADD FOREIGN KEY (event_id) REFERENCES user_events (id) ON DELETE CASCADE; - -ALTER TABLE user_list -ADD FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE; - -ALTER TABLE user_events -ADD FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE; - -ALTER TABLE user_events -ADD FOREIGN KEY (project_id) REFERENCES projects (id) ON DELETE CASCADE; - -ALTER TABLE rule_evaluations -ADD FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE; - -ALTER TABLE rule_evaluations -ADD FOREIGN KEY (rule_id) REFERENCES rules (id) ON DELETE CASCADE; - -ALTER TABLE project_rule_paths -ADD FOREIGN KEY (project_id) REFERENCES projects (id) ON DELETE CASCADE; - -ALTER TABLE journey_steps -ADD FOREIGN KEY (journey_id) REFERENCES journeys (id) ON DELETE CASCADE; - -ALTER TABLE journey_steps -ADD FOREIGN KEY (child_id) REFERENCES journey_steps (id) ON DELETE SET NULL; - -ALTER TABLE journey_user_step -ADD FOREIGN KEY (journey_id) REFERENCES journeys (id) ON DELETE CASCADE; - -ALTER TABLE journey_user_step -ADD FOREIGN KEY (entrance_id) REFERENCES journey_user_step (id) ON DELETE CASCADE; - -ALTER TABLE journey_user_step -ADD FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE; - -ALTER TABLE journey_user_step -ADD FOREIGN KEY (step_id) REFERENCES journey_steps (id) ON DELETE CASCADE; - -ALTER TABLE organizations -ADD FOREIGN KEY (notification_provider_id) REFERENCES providers (id) ON DELETE SET NULL; - -ALTER TABLE projects -ADD FOREIGN KEY (organization_id) REFERENCES organizations (id) ON DELETE CASCADE; - -ALTER TABLE journeys -ADD FOREIGN KEY (parent_id) REFERENCES journeys (id) ON DELETE CASCADE; - -ALTER TABLE journeys -ADD FOREIGN KEY (project_id) REFERENCES projects (id) ON DELETE CASCADE; - -ALTER TABLE lists -ADD FOREIGN KEY (rule_id) REFERENCES rules (id) ON DELETE SET NULL; - -ALTER TABLE lists -ADD FOREIGN KEY (project_id) REFERENCES projects (id) ON DELETE CASCADE; - -ALTER TABLE locales -ADD FOREIGN KEY (project_id) REFERENCES projects (id) ON DELETE CASCADE; - -ALTER TABLE notifications -ADD FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE; - -ALTER TABLE notifications -ADD FOREIGN KEY (project_id) REFERENCES projects (id) ON DELETE CASCADE; - -ALTER TABLE project_admins -ADD FOREIGN KEY (admin_id) REFERENCES admins (id) ON DELETE CASCADE; - -ALTER TABLE project_admins -ADD FOREIGN KEY (project_id) REFERENCES projects (id) ON DELETE CASCADE; - -ALTER TABLE project_api_keys -ADD FOREIGN KEY (project_id) REFERENCES projects (id) ON DELETE CASCADE; - -ALTER TABLE users -ADD FOREIGN KEY (project_id) REFERENCES projects (id) ON DELETE CASCADE; - -ALTER TABLE providers -ADD FOREIGN KEY (project_id) REFERENCES projects (id) ON DELETE CASCADE; - -ALTER TABLE subscriptions -ADD FOREIGN KEY (project_id) REFERENCES projects (id) ON DELETE CASCADE; - -ALTER TABLE tags -ADD FOREIGN KEY (project_id) REFERENCES projects (id) ON DELETE CASCADE; - -ALTER TABLE rules -ADD FOREIGN KEY (parent_uuid) REFERENCES rules (uuid) ON DELETE CASCADE; - -ALTER TABLE rules -ADD FOREIGN KEY (root_uuid) REFERENCES rules (uuid) ON DELETE CASCADE; - -ALTER TABLE rules -ADD FOREIGN KEY (project_id) REFERENCES projects (id) ON DELETE CASCADE; - -ALTER TABLE templates -ADD FOREIGN KEY (project_id) REFERENCES projects (id) ON DELETE CASCADE; - -ALTER TABLE templates -ADD FOREIGN KEY (campaign_id) REFERENCES campaigns (id) ON DELETE CASCADE; - `) -} - -exports.down = async function (knex) { -} diff --git a/services/platform/db/migrations/1760170331_user_journey_indexes.js b/services/platform/db/migrations/1760170331_user_journey_indexes.js deleted file mode 100644 index b86d93fe..00000000 --- a/services/platform/db/migrations/1760170331_user_journey_indexes.js +++ /dev/null @@ -1,13 +0,0 @@ -exports.up = async function (knex) { - await knex.schema.alterTable('journey_user_step', table => { - table.index(['entrance_id'], 'user_journey_step_entrance_id_index') - table.index(['journey_id'], 'user_journey_step_journey_id_index') - }) -} - -exports.down = async function (knex) { - await knex.schema.alterTable('journey_user_step', table => { - table.dropIndex(['entrance_id'], 'user_journey_step_entrance_id_index') - table.dropIndex(['journey_id'], 'user_journey_step_journey_id_index') - }) -} diff --git a/services/platform/db/migrations/1760294845_organizations.js b/services/platform/db/migrations/1760294845_organizations.js deleted file mode 100644 index d0619c7a..00000000 --- a/services/platform/db/migrations/1760294845_organizations.js +++ /dev/null @@ -1,15 +0,0 @@ -exports.up = async function (knex) { - await knex.schema.alterTable('organizations', table => { - table.dropColumn('domain'); - table.dropColumn('username'); - table.string('name').notNullable().defaultTo(''); - }) -} - -exports.down = async function (knex) { - await knex.schema.alterTable('organizations', table => { - table.dropColumn('name'); - table.string('domain').notNullable().defaultTo(''); - table.string('username').notNullable().defaultTo(''); - }) -} diff --git a/services/platform/db/migrations/1760303889_admins_external_id.js b/services/platform/db/migrations/1760303889_admins_external_id.js deleted file mode 100644 index ddc3d24d..00000000 --- a/services/platform/db/migrations/1760303889_admins_external_id.js +++ /dev/null @@ -1,11 +0,0 @@ -exports.up = async function (knex) { - await knex.schema.alterTable('admins', table => { - table.string('external_id'); - }) -} - -exports.down = async function (knex) { - await knex.schema.alterTable('admins', table => { - table.dropColumn('external_id'); - }) -} diff --git a/services/platform/db/migrations/1760445524_bootstrap_providers.js b/services/platform/db/migrations/1760445524_bootstrap_providers.js deleted file mode 100644 index 725c7d80..00000000 --- a/services/platform/db/migrations/1760445524_bootstrap_providers.js +++ /dev/null @@ -1,15 +0,0 @@ -exports.up = async function (knex) { - await knex.schema.alterTable('providers', table => { - table.string('type').notNullable().alter(); - table.string('name').notNullable().alter(); - table.string('external_id'); - }) -} - -exports.down = async function (knex) { - await knex.schema.alterTable('providers', table => { - table.string('type').nullable().alter(); - table.string('name').nullable().alter(); - table.dropColumn('external_id'); - }) -} diff --git a/services/platform/db/migrations/1760715480_project_tools.js b/services/platform/db/migrations/1760715480_project_tools.js deleted file mode 100644 index 4528872a..00000000 --- a/services/platform/db/migrations/1760715480_project_tools.js +++ /dev/null @@ -1,11 +0,0 @@ -exports.up = async function (knex) { - await knex.schema.alterTable('projects', table => { - table.specificType('tools', 'text[]').nullable(); - }) -} - -exports.down = async function (knex) { - await knex.schema.alterTable('projects', table => { - table.dropColumn('tools'); - }) -} diff --git a/services/platform/db/migrations/1762349315_campaigns.js b/services/platform/db/migrations/1762349315_campaigns.js deleted file mode 100644 index a7942cb1..00000000 --- a/services/platform/db/migrations/1762349315_campaigns.js +++ /dev/null @@ -1,15 +0,0 @@ -exports.up = async function (knex) { - await knex.schema.alterTable('campaigns', table => { - table.uuid('provider_id').nullable().alter(); - table.uuid('subscription_id').nullable().alter(); - table.dropColumn('name'); - }) -} - -exports.down = async function (knex) { - await knex.schema.alterTable('campaigns', table => { - table.uuid('provider_id').notNullable().alter(); - table.uuid('subscription_id').notNullable().alter(); - table.string('name').nullable(); - }) -} diff --git a/services/platform/scripts/create-migration.mjs b/services/platform/scripts/create-migration.mjs deleted file mode 100644 index 0dfaed72..00000000 --- a/services/platform/scripts/create-migration.mjs +++ /dev/null @@ -1,35 +0,0 @@ -import knex from 'knex' - -const connection = knex({ - client: 'pg', - connection: { - host: process.env.DB_HOST, - user: process.env.DB_USERNAME, - password: process.env.DB_PASSWORD, - port: process.env.DB_PORT, - database: process.env.DB_DATABASE, - }, -}) - -const migrationConfig = { - directory: './db/migrations', - tableName: 'migrations', - stub: './db/migration.stub', -} - -const name = process.argv[2] -if (!name) { - console.log('migration: please include a name for migration') - process.exit(9) -} - -connection.migrate.make(name, migrationConfig) - .then(() => { - console.log('migration create finished') - process.exit() - }) - .catch((err) => { - console.error('migration create failed') - console.error(err) - process.exit(1) - }) diff --git a/services/platform/scripts/output-migration.mjs b/services/platform/scripts/output-migration.mjs deleted file mode 100644 index 770a8b4d..00000000 --- a/services/platform/scripts/output-migration.mjs +++ /dev/null @@ -1,49 +0,0 @@ -import knex from 'knex' - -const connection = knex({ - client: process.env.DB_CLIENT ?? 'pq', - connection: { - host: process.env.DB_HOST, - user: process.env.DB_USERNAME, - password: process.env.DB_PASSWORD, - port: process.env.DB_PORT, - database: process.env.DB_DATABASE, - }, -}) - -const name = process.argv[2] -if (!name) { - console.log('migration: please include a migration to output') - process.exit(9) -} - -const logRaw = (sql) => { - const end = sql.charAt(sql.length - 1) - console.log(end !== ';' ? sql + ';' : sql) -} - -const migration = await import(`../db/migrations/${name}`) -const method = (type) => { - return (name, query) => { - const schema = connection.schema - const result = schema[type](name, query) - logRaw(result.toString()) - return options.schema - } -} - -const options = { - schema: { - table: method('table'), - createTable: method('createTable'), - alterTable: method('alterTable'), - dropTable: method('dropTable'), - }, - raw: (query) => logRaw(query), -} - -console.log('up') -migration.up(options).then(() => { - console.log('down') - migration.down(options) -}) diff --git a/services/platform/src/config/database.ts b/services/platform/src/config/database.ts index 422931ca..3dcbec4e 100644 --- a/services/platform/src/config/database.ts +++ b/services/platform/src/config/database.ts @@ -1,6 +1,5 @@ import knex, { Knex as Database } from 'knex' -import path from 'path' -import { removeKey, sleep } from '../utilities' +import { removeKey } from '../utilities' import { logger } from './logger' export { Database } @@ -16,8 +15,6 @@ export interface DatabaseConfig { export type Query = (builder: Database.QueryBuilder) => Database.QueryBuilder -const MIGRATION_RETRIES = 3 - knex.QueryBuilder.extend('when', function( condition: boolean, fnif: Query, @@ -46,57 +43,13 @@ const connect = (config: DatabaseConfig, withDB = true) => { }) } -const migrate = async (config: DatabaseConfig, db: Database, retries = MIGRATION_RETRIES): Promise => { - try { - return await db.migrate.latest({ - directory: [ - path.resolve(__dirname, '../../db/migrations'), - ...config.migrationPaths, - ], - tableName: 'migrations', - loadExtensions: ['.js', '.ts'], - }) - } catch (error: any) { - if (error?.name === 'MigrationLocked' && retries > 0) { - --retries - await sleep((MIGRATION_RETRIES - retries) * 1000) - return await migrate(config, db, retries) - } - throw error - } -} - -const createDatabase = async (config: DatabaseConfig, db: Database) => { - try { - await db.raw(`CREATE DATABASE ${config.database}`) - } catch (error: any) { - if (error.errno !== 1007) throw error - } -} - export default async (config: DatabaseConfig) => { // Attempt to connect & migrate try { - const db = connect(config) - await migrate(config, db) - return db + return connect(config) } catch (error: any) { - - // Check if error is related to DB not existing - if (error?.errno === 1049) { - - // Connect without database and create it - let db = connect(config, false) - await createDatabase(config, db) - - // Reconnect using new database - db = connect(config) - await migrate(config, db) - return db - } else { - logger.error(error, 'database error') - throw error - } + logger.error(error, 'database error') + throw error } }