From 79bc3eed46159297e6fa6125c12ec00a52bb570b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dawid=20Wysoki=C5=84ski?= Date: Mon, 21 Nov 2022 05:54:42 +0000 Subject: [PATCH] feat: add a new REST endpoint - GET /api/v1/user (#7) Reviewed-on: https://gitea.dwysokinski.me/twhelp/sessions/pulls/7 --- Makefile | 9 +- cmd/sessions/internal/serve/serve.go | 50 ++++++ go.mod | 16 +- go.sum | 50 +++++- internal/bundb/api_key.go | 21 +++ internal/bundb/api_key_test.go | 28 +++- internal/bundb/internal/model/api_key.go | 18 +-- internal/bundb/internal/model/api_key_test.go | 3 - internal/domain/api_key.go | 26 ++- internal/domain/api_key_test.go | 13 ++ internal/router/rest/internal/docs/.gitignore | 3 + .../internal/mock/api_key_verifier.gen.go | 120 ++++++++++++++ internal/router/rest/internal/model/error.go | 11 ++ internal/router/rest/internal/model/user.go | 29 ++++ internal/router/rest/mw_auth.go | 50 ++++++ internal/router/rest/openapi.go | 18 +++ internal/router/rest/rest.go | 151 ++++++++++++++++++ internal/router/rest/rest_test.go | 113 +++++++++++++ internal/router/rest/user.go | 25 +++ internal/router/rest/user_test.go | 118 ++++++++++++++ internal/service/api_key.go | 22 +++ internal/service/api_key_test.go | 42 +++++ internal/tools/go.mod | 17 +- internal/tools/go.sum | 43 ++++- internal/tools/tools.go | 1 + k8s/base/api.yml | 2 + 26 files changed, 969 insertions(+), 30 deletions(-) create mode 100644 internal/router/rest/internal/docs/.gitignore create mode 100644 internal/router/rest/internal/mock/api_key_verifier.gen.go create mode 100644 internal/router/rest/internal/model/error.go create mode 100644 internal/router/rest/internal/model/user.go create mode 100644 internal/router/rest/mw_auth.go create mode 100644 internal/router/rest/openapi.go create mode 100644 internal/router/rest/rest.go create mode 100644 internal/router/rest/rest_test.go create mode 100644 internal/router/rest/user.go create mode 100644 internal/router/rest/user_test.go diff --git a/Makefile b/Makefile index 4bdd969..586ecb9 100644 --- a/Makefile +++ b/Makefile @@ -9,18 +9,23 @@ install-counterfeiter: @echo "Installing github.com/maxbrunsfeld/counterfeiter..." cd ./internal/tools && go install github.com/maxbrunsfeld/counterfeiter/v6 +.PHONY: install-swag +install-swag: + @echo "Installing github.com/swaggo/swag..." + cd ./internal/tools && go install github.com/swaggo/swag/cmd/swag + .PHONY: install-golangci-lint install-golangci-lint: @echo "Installing github.com/golangci/golangci-lint..." cd ./internal/tools && go install github.com/golangci/golangci-lint/cmd/golangci-lint .PHONY: install-tools -install-tools: install-golangci-lint install-counterfeiter +install-tools: install-golangci-lint install-counterfeiter install-swag .PHONY: install install: install-tools install-git-hooks .PHONY: generate -generate: install-counterfeiter +generate: install-counterfeiter install-swag @echo "Running go generate..." go generate ./... \ No newline at end of file diff --git a/cmd/sessions/internal/serve/serve.go b/cmd/sessions/internal/serve/serve.go index 1383101..3a49257 100644 --- a/cmd/sessions/internal/serve/serve.go +++ b/cmd/sessions/internal/serve/serve.go @@ -15,6 +15,9 @@ import ( "gitea.dwysokinski.me/twhelp/sessions/cmd/sessions/internal" "gitea.dwysokinski.me/twhelp/sessions/internal/bundb" "gitea.dwysokinski.me/twhelp/sessions/internal/router/meta" + "gitea.dwysokinski.me/twhelp/sessions/internal/router/rest" + "gitea.dwysokinski.me/twhelp/sessions/internal/service" + "github.com/kelseyhightower/envconfig" "github.com/uptrace/bun" "github.com/urfave/cli/v2" @@ -80,10 +83,38 @@ type serverConfig struct { } func newServer(cfg serverConfig) (*http.Server, error) { + apiCfg, err := newAPIConfig() + if err != nil { + return nil, fmt.Errorf("newAPIConfig: %w", err) + } + + // repos + userRepo := bundb.NewUser(cfg.db) + apiKeyRepo := bundb.NewAPIKey(cfg.db) + + // services + userSvc := service.NewUser(userRepo) + apiKeySvc := service.NewAPIKey(apiKeyRepo, userSvc) + // router r := chi.NewRouter() r.Use(getChiMiddlewares(cfg.logger)...) r.Mount(metaEndpointsPrefix, meta.NewRouter([]meta.Checker{bundb.NewChecker(cfg.db)})) + r.Mount("/api", rest.NewRouter(rest.RouterConfig{ + APIKeyVerifier: apiKeySvc, + CORS: rest.CORSConfig{ + Enabled: apiCfg.CORSEnabled, + AllowedOrigins: apiCfg.CORSAllowedOrigins, + AllowedMethods: apiCfg.CORSAllowedMethods, + AllowCredentials: apiCfg.CORSAllowCredentials, + MaxAge: int(apiCfg.CORSMaxAge), + }, + Swagger: rest.SwaggerConfig{ + Enabled: apiCfg.SwaggerEnabled, + Host: apiCfg.SwaggerHost, + Schemes: apiCfg.SwaggerSchemes, + }, + })) return &http.Server{ Addr: ":" + defaultPort, @@ -95,6 +126,25 @@ func newServer(cfg serverConfig) (*http.Server, error) { }, nil } +type apiConfig struct { + SwaggerEnabled bool `envconfig:"SWAGGER_ENABLED" default:"true"` + SwaggerHost string `envconfig:"SWAGGER_HOST" default:""` + SwaggerSchemes []string `envconfig:"SWAGGER_SCHEMES" default:"http,https"` + CORSEnabled bool `envconfig:"CORS_ENABLED" default:"false"` + CORSAllowedOrigins []string `envconfig:"CORS_ALLOWED_ORIGINS" default:""` + CORSAllowCredentials bool `envconfig:"CORS_ALLOW_CREDENTIALS" default:"false"` + CORSAllowedMethods []string `envconfig:"CORS_ALLOWED_METHODS" default:"HEAD,GET"` + CORSMaxAge uint32 `envconfig:"CORS_MAX_AGE" default:"300"` +} + +func newAPIConfig() (apiConfig, error) { + var cfg apiConfig + if err := envconfig.Process("API", &cfg); err != nil { + return apiConfig{}, fmt.Errorf("envconfig.Process: %w", err) + } + return cfg, nil +} + func getChiMiddlewares(logger *zap.Logger) chi.Middlewares { return chi.Middlewares{ middleware.RealIP, diff --git a/go.mod b/go.mod index 6c7f155..e30fcae 100644 --- a/go.mod +++ b/go.mod @@ -6,12 +6,15 @@ require ( gitea.dwysokinski.me/Kichiyaki/chizap v0.2.1 github.com/cenkalti/backoff/v4 v4.2.0 github.com/go-chi/chi/v5 v5.0.7 + github.com/go-chi/cors v1.2.1 github.com/google/go-cmp v0.5.9 github.com/google/uuid v1.3.0 github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa github.com/kelseyhightower/envconfig v1.4.0 github.com/ory/dockertest/v3 v3.9.1 github.com/stretchr/testify v1.8.1 + github.com/swaggo/http-swagger v1.3.3 + github.com/swaggo/swag v1.8.7 github.com/uptrace/bun v1.1.8 github.com/uptrace/bun/dbfixture v1.1.8 github.com/uptrace/bun/dialect/pgdialect v1.1.8 @@ -22,6 +25,7 @@ require ( require ( github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect + github.com/KyleBanks/depth v1.2.1 // indirect github.com/Microsoft/go-winio v0.5.2 // indirect github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect github.com/containerd/continuity v0.3.0 // indirect @@ -31,10 +35,16 @@ require ( github.com/docker/docker v20.10.7+incompatible // indirect github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-units v0.4.0 // indirect + github.com/go-openapi/jsonpointer v0.19.5 // indirect + github.com/go-openapi/jsonreference v0.20.0 // indirect + github.com/go-openapi/spec v0.20.6 // indirect + github.com/go-openapi/swag v0.19.15 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/imdario/mergo v0.3.12 // indirect github.com/jinzhu/inflection v1.0.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/mailru/easyjson v0.7.6 // indirect github.com/mitchellh/mapstructure v1.4.1 // indirect github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect @@ -44,6 +54,7 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sirupsen/logrus v1.8.1 // indirect + github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe // indirect github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect @@ -54,9 +65,10 @@ require ( go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.7.0 // indirect golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d // indirect - golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect + golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64 // indirect - gopkg.in/yaml.v2 v2.3.0 // indirect + golang.org/x/tools v0.1.12 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect mellium.im/sasl v0.3.0 // indirect ) diff --git a/go.sum b/go.sum index 12c228c..d14a5f0 100644 --- a/go.sum +++ b/go.sum @@ -3,6 +3,8 @@ gitea.dwysokinski.me/Kichiyaki/chizap v0.2.1/go.mod h1:q+HrVxr1rf95wyuSTBt4Sa54U github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= +github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= @@ -19,6 +21,7 @@ github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= @@ -36,6 +39,18 @@ github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDD github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8= github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= +github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA= +github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= +github.com/go-openapi/spec v0.20.6 h1:ich1RQ3WDbfoeTqTAb+5EIxNmpKVJZWBNah9RAT0jIQ= +github.com/go-openapi/spec v0.20.6/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM= +github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= @@ -58,20 +73,31 @@ github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa h1:s+4MhCQ6YrzisK6 github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +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/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lib/pq v0.0.0-20180327071824-d34b9ff171c2 h1:hRGSmZu7j271trc9sneMrpOW7GN5ngLm8YUZIPzf394= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU= github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 h1:rzf0wL0CHVc8CEsgyygG0Mn9CNCCPZqOPaz8RiiHYQk= github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc= github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= 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.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= @@ -107,6 +133,12 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe h1:K8pHPVoTgxFJt1lXuIzzOX7zZhZFldJQK/CgKx9BFIc= +github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe/go.mod h1:lKJPbtWzJ9JhsTN1k1gZgleJWY/cqq0psdoMmaThG3w= +github.com/swaggo/http-swagger v1.3.3 h1:Hu5Z0L9ssyBLofaama21iYaF2VbWyA8jdohaaCGpHsc= +github.com/swaggo/http-swagger v1.3.3/go.mod h1:sE+4PjD89IxMPm77FnkDz0sdO+p5lbXzrVWT6OTVVGo= +github.com/swaggo/swag v1.8.7 h1:2K9ivTD3teEO+2fXV6zrZKDqk5IuU2aJtBDo8U7omWU= +github.com/swaggo/swag v1.8.7/go.mod h1:ezQVUUhly8dludpVk+/PuwJWvLLanB13ygV5Pr9enSk= github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= @@ -152,14 +184,16 @@ golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d h1:3qF+Z8Hkrw9sOhrFHti9Tl golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 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.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= 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-20190620200207-3b0461eec859/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-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 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-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -172,6 +206,7 @@ golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7w 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-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -180,23 +215,30 @@ golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 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/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 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/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 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-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= 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-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/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= diff --git a/internal/bundb/api_key.go b/internal/bundb/api_key.go index 29dcbd4..1aadc19 100644 --- a/internal/bundb/api_key.go +++ b/internal/bundb/api_key.go @@ -2,6 +2,7 @@ package bundb import ( "context" + "database/sql" "errors" "fmt" @@ -36,6 +37,26 @@ func (a *APIKey) Create(ctx context.Context, params domain.CreateAPIKeyParams) ( return apiKey.ToDomain(), nil } +func (a *APIKey) Get(ctx context.Context, key string) (domain.APIKey, error) { + var ak model.APIKey + + if err := a.db.NewSelect(). + Model(&ak). + Where("key = ?", key). + Scan(ctx); err != nil { + if errors.Is(err, sql.ErrNoRows) { + return domain.APIKey{}, domain.APIKeyNotFoundError{Key: key} + } + return domain.APIKey{}, fmt.Errorf( + "something went wrong while selecting api key (key=%s) from the db: %w", + key, + err, + ) + } + + return ak.ToDomain(), nil +} + func mapCreateAPIKeyError(err error, params domain.CreateAPIKeyParams) error { var pgError pgdriver.Error if !errors.As(err, &pgError) { diff --git a/internal/bundb/api_key_test.go b/internal/bundb/api_key_test.go index dd72981..e7ae798 100644 --- a/internal/bundb/api_key_test.go +++ b/internal/bundb/api_key_test.go @@ -33,7 +33,6 @@ func TestAPIKey_Create(t *testing.T) { assert.Equal(t, params.Key(), apiKey.Key) assert.Equal(t, params.UserID(), apiKey.UserID) assert.WithinDuration(t, time.Now(), apiKey.CreatedAt, time.Second) - assert.Zero(t, apiKey.LastUsedAt) }) t.Run("ERR: player doesn't exist", func(t *testing.T) { @@ -66,3 +65,30 @@ func TestAPIKey_Create(t *testing.T) { assert.Zero(t, apiKey) }) } + +func TestAPIKey_Get(t *testing.T) { + t.Parallel() + + db := newDB(t) + fixture := loadFixtures(t, db) + repo := bundb.NewAPIKey(db) + apiKeyFromFixture := fixture.apiKey(t, "user-1-api-key-1") + + t.Run("OK", func(t *testing.T) { + t.Parallel() + + ak, err := repo.Get(context.Background(), apiKeyFromFixture.Key) + assert.NoError(t, err) + assert.Equal(t, apiKeyFromFixture, ak) + }) + + t.Run("ERR: API key not found", func(t *testing.T) { + t.Parallel() + + ak, err := repo.Get(context.Background(), apiKeyFromFixture.Key+"1") + assert.ErrorIs(t, err, domain.APIKeyNotFoundError{ + Key: apiKeyFromFixture.Key + "1", + }) + assert.Zero(t, ak) + }) +} diff --git a/internal/bundb/internal/model/api_key.go b/internal/bundb/internal/model/api_key.go index d35b055..3d54246 100644 --- a/internal/bundb/internal/model/api_key.go +++ b/internal/bundb/internal/model/api_key.go @@ -10,11 +10,10 @@ import ( type APIKey struct { bun.BaseModel `bun:"base_model,table:api_keys,alias:ak"` - ID int64 `bun:"id,pk,autoincrement,identity"` - Key string `bun:"key,nullzero,type:varchar(255),notnull,unique"` - UserID int64 `bun:"user_id,nullzero,notnull"` - CreatedAt time.Time `bun:"created_at,nullzero,notnull,default:current_timestamp"` - LastUsedAt time.Time `bun:"last_used_at,nullzero"` + ID int64 `bun:"id,pk,autoincrement,identity"` + Key string `bun:"key,nullzero,type:varchar(255),notnull,unique"` + UserID int64 `bun:"user_id,nullzero,notnull"` + CreatedAt time.Time `bun:"created_at,nullzero,notnull,default:current_timestamp"` } func NewAPIKey(p domain.CreateAPIKeyParams) APIKey { @@ -27,10 +26,9 @@ func NewAPIKey(p domain.CreateAPIKeyParams) APIKey { func (a *APIKey) ToDomain() domain.APIKey { return domain.APIKey{ - ID: a.ID, - Key: a.Key, - UserID: a.UserID, - CreatedAt: a.CreatedAt, - LastUsedAt: a.LastUsedAt, + ID: a.ID, + Key: a.Key, + UserID: a.UserID, + CreatedAt: a.CreatedAt, } } diff --git a/internal/bundb/internal/model/api_key_test.go b/internal/bundb/internal/model/api_key_test.go index 2d339cb..7d132e8 100644 --- a/internal/bundb/internal/model/api_key_test.go +++ b/internal/bundb/internal/model/api_key_test.go @@ -14,18 +14,15 @@ func TestAPIKey(t *testing.T) { t.Parallel() var id int64 = 123 - lastUsedAt := time.Now().Add(-5 * time.Minute) params, err := domain.NewCreateAPIKeyParams(uuid.NewString(), 1) assert.NoError(t, err) result := model.NewAPIKey(params) result.ID = id - result.LastUsedAt = lastUsedAt apiKey := result.ToDomain() assert.Equal(t, id, apiKey.ID) assert.Equal(t, params.Key(), apiKey.Key) assert.Equal(t, params.UserID(), apiKey.UserID) assert.WithinDuration(t, time.Now(), apiKey.CreatedAt, 10*time.Millisecond) - assert.Equal(t, lastUsedAt, apiKey.LastUsedAt) } diff --git a/internal/domain/api_key.go b/internal/domain/api_key.go index 59c0b26..b3e73e5 100644 --- a/internal/domain/api_key.go +++ b/internal/domain/api_key.go @@ -1,6 +1,7 @@ package domain import ( + "fmt" "time" ) @@ -9,11 +10,10 @@ const ( ) type APIKey struct { - ID int64 - Key string - UserID int64 - CreatedAt time.Time - LastUsedAt time.Time + ID int64 + Key string + UserID int64 + CreatedAt time.Time } type CreateAPIKeyParams struct { @@ -48,3 +48,19 @@ func (c CreateAPIKeyParams) Key() string { func (c CreateAPIKeyParams) UserID() int64 { return c.userID } + +type APIKeyNotFoundError struct { + Key string +} + +func (e APIKeyNotFoundError) Error() string { + return fmt.Sprintf("API key (key=%s) not found", e.Key) +} + +func (e APIKeyNotFoundError) UserError() string { + return e.Error() +} + +func (e APIKeyNotFoundError) Code() ErrorCode { + return ErrorCodeEntityNotFound +} diff --git a/internal/domain/api_key_test.go b/internal/domain/api_key_test.go index 9f58764..1dc37d5 100644 --- a/internal/domain/api_key_test.go +++ b/internal/domain/api_key_test.go @@ -1,6 +1,7 @@ package domain_test import ( + "fmt" "testing" "gitea.dwysokinski.me/twhelp/sessions/internal/domain" @@ -62,3 +63,15 @@ func TestNewCreateAPIKeyParams(t *testing.T) { }) } } + +func TestAPIKeyNotFoundError(t *testing.T) { + t.Parallel() + + err := domain.APIKeyNotFoundError{ + Key: uuid.NewString(), + } + var _ domain.Error = err + assert.Equal(t, fmt.Sprintf("API key (key=%s) not found", err.Key), err.Error()) + assert.Equal(t, err.Error(), err.UserError()) + assert.Equal(t, domain.ErrorCodeEntityNotFound, err.Code()) +} diff --git a/internal/router/rest/internal/docs/.gitignore b/internal/router/rest/internal/docs/.gitignore new file mode 100644 index 0000000..38d15fe --- /dev/null +++ b/internal/router/rest/internal/docs/.gitignore @@ -0,0 +1,3 @@ +*.go +swagger.json +swagger.yaml \ No newline at end of file diff --git a/internal/router/rest/internal/mock/api_key_verifier.gen.go b/internal/router/rest/internal/mock/api_key_verifier.gen.go new file mode 100644 index 0000000..138cbdc --- /dev/null +++ b/internal/router/rest/internal/mock/api_key_verifier.gen.go @@ -0,0 +1,120 @@ +// Code generated by counterfeiter. DO NOT EDIT. +package mock + +import ( + "context" + "sync" + + "gitea.dwysokinski.me/twhelp/sessions/internal/domain" + "gitea.dwysokinski.me/twhelp/sessions/internal/router/rest" +) + +type FakeAPIKeyVerifier struct { + VerifyStub func(context.Context, string) (domain.User, error) + verifyMutex sync.RWMutex + verifyArgsForCall []struct { + arg1 context.Context + arg2 string + } + verifyReturns struct { + result1 domain.User + result2 error + } + verifyReturnsOnCall map[int]struct { + result1 domain.User + result2 error + } + invocations map[string][][]interface{} + invocationsMutex sync.RWMutex +} + +func (fake *FakeAPIKeyVerifier) Verify(arg1 context.Context, arg2 string) (domain.User, error) { + fake.verifyMutex.Lock() + ret, specificReturn := fake.verifyReturnsOnCall[len(fake.verifyArgsForCall)] + fake.verifyArgsForCall = append(fake.verifyArgsForCall, struct { + arg1 context.Context + arg2 string + }{arg1, arg2}) + stub := fake.VerifyStub + fakeReturns := fake.verifyReturns + fake.recordInvocation("Verify", []interface{}{arg1, arg2}) + fake.verifyMutex.Unlock() + if stub != nil { + return stub(arg1, arg2) + } + if specificReturn { + return ret.result1, ret.result2 + } + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *FakeAPIKeyVerifier) VerifyCallCount() int { + fake.verifyMutex.RLock() + defer fake.verifyMutex.RUnlock() + return len(fake.verifyArgsForCall) +} + +func (fake *FakeAPIKeyVerifier) VerifyCalls(stub func(context.Context, string) (domain.User, error)) { + fake.verifyMutex.Lock() + defer fake.verifyMutex.Unlock() + fake.VerifyStub = stub +} + +func (fake *FakeAPIKeyVerifier) VerifyArgsForCall(i int) (context.Context, string) { + fake.verifyMutex.RLock() + defer fake.verifyMutex.RUnlock() + argsForCall := fake.verifyArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2 +} + +func (fake *FakeAPIKeyVerifier) VerifyReturns(result1 domain.User, result2 error) { + fake.verifyMutex.Lock() + defer fake.verifyMutex.Unlock() + fake.VerifyStub = nil + fake.verifyReturns = struct { + result1 domain.User + result2 error + }{result1, result2} +} + +func (fake *FakeAPIKeyVerifier) VerifyReturnsOnCall(i int, result1 domain.User, result2 error) { + fake.verifyMutex.Lock() + defer fake.verifyMutex.Unlock() + fake.VerifyStub = nil + if fake.verifyReturnsOnCall == nil { + fake.verifyReturnsOnCall = make(map[int]struct { + result1 domain.User + result2 error + }) + } + fake.verifyReturnsOnCall[i] = struct { + result1 domain.User + result2 error + }{result1, result2} +} + +func (fake *FakeAPIKeyVerifier) Invocations() map[string][][]interface{} { + fake.invocationsMutex.RLock() + defer fake.invocationsMutex.RUnlock() + fake.verifyMutex.RLock() + defer fake.verifyMutex.RUnlock() + copiedInvocations := map[string][][]interface{}{} + for key, value := range fake.invocations { + copiedInvocations[key] = value + } + return copiedInvocations +} + +func (fake *FakeAPIKeyVerifier) recordInvocation(key string, args []interface{}) { + fake.invocationsMutex.Lock() + defer fake.invocationsMutex.Unlock() + if fake.invocations == nil { + fake.invocations = map[string][][]interface{}{} + } + if fake.invocations[key] == nil { + fake.invocations[key] = [][]interface{}{} + } + fake.invocations[key] = append(fake.invocations[key], args) +} + +var _ rest.APIKeyVerifier = new(FakeAPIKeyVerifier) diff --git a/internal/router/rest/internal/model/error.go b/internal/router/rest/internal/model/error.go new file mode 100644 index 0000000..9eff4e8 --- /dev/null +++ b/internal/router/rest/internal/model/error.go @@ -0,0 +1,11 @@ +package model + +type APIError struct { + //nolint:lll + Code string `json:"code" enums:"entity-not-found,route-not-found,method-not-allowed,validation-error,unauthorized,already-exists,internal-server-error"` + Message string `json:"message"` +} // @name ApiError + +type ErrorResp struct { + Error APIError `json:"error"` +} // @name ErrorResp diff --git a/internal/router/rest/internal/model/user.go b/internal/router/rest/internal/model/user.go new file mode 100644 index 0000000..8e3413e --- /dev/null +++ b/internal/router/rest/internal/model/user.go @@ -0,0 +1,29 @@ +package model + +import ( + "time" + + "gitea.dwysokinski.me/twhelp/sessions/internal/domain" +) + +type User struct { + ID int64 `json:"id"` + Name string `json:"name"` + CreatedAt time.Time `json:"createdAt" format:"date-time"` +} // @name User + +func NewUser(u domain.User) User { + return User{ + ID: u.ID, + Name: u.Name, + CreatedAt: u.CreatedAt, + } +} + +type GetUserResp struct { + Data User `json:"data"` +} // @name GetUserResp + +func NewGetUserResp(u domain.User) GetUserResp { + return GetUserResp{Data: NewUser(u)} +} diff --git a/internal/router/rest/mw_auth.go b/internal/router/rest/mw_auth.go new file mode 100644 index 0000000..9b181c7 --- /dev/null +++ b/internal/router/rest/mw_auth.go @@ -0,0 +1,50 @@ +package rest + +import ( + "context" + "net/http" + + "gitea.dwysokinski.me/twhelp/sessions/internal/domain" + "gitea.dwysokinski.me/twhelp/sessions/internal/router/rest/internal/model" +) + +type authCtxKey struct{} + +func authMiddleware(verifier APIKeyVerifier) func(http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + key := r.Header.Get("X-API-Key") + if key == "" { + renderJSON(w, http.StatusUnauthorized, model.ErrorResp{ + Error: model.APIError{ + Code: "unauthorized", + Message: "invalid API key", + }, + }) + return + } + + u, err := verifier.Verify(r.Context(), key) + if err != nil { + renderJSON(w, http.StatusUnauthorized, model.ErrorResp{ + Error: model.APIError{ + Code: "unauthorized", + Message: "invalid API key", + }, + }) + return + } + + next.ServeHTTP(w, r.WithContext(userToContext(r.Context(), u))) + }) + } +} + +func userToContext(ctx context.Context, u domain.User) context.Context { + return context.WithValue(ctx, authCtxKey{}, u) +} + +func userFromContext(ctx context.Context) (domain.User, bool) { + u, ok := ctx.Value(authCtxKey{}).(domain.User) + return u, ok +} diff --git a/internal/router/rest/openapi.go b/internal/router/rest/openapi.go new file mode 100644 index 0000000..f66a6d3 --- /dev/null +++ b/internal/router/rest/openapi.go @@ -0,0 +1,18 @@ +package rest + +//go:generate swag init -g openapi.go -o "./internal/docs" + +// @title Sessions API +// @version 1.0 + +// @contact.name Dawid WysokiƄski +// @contact.url https://dwysokinski.me +// @contact.email contact@dwysokinski.me + +// @BasePath /api/v1 + +// @query.collection.format multi + +// @securityDefinitions.apikey ApiKeyAuth +// @in header +// @name X-Api-Key diff --git a/internal/router/rest/rest.go b/internal/router/rest/rest.go new file mode 100644 index 0000000..78000a8 --- /dev/null +++ b/internal/router/rest/rest.go @@ -0,0 +1,151 @@ +package rest + +import ( + "context" + "encoding/json" + "net/http" + + "gitea.dwysokinski.me/twhelp/sessions/internal/domain" + "gitea.dwysokinski.me/twhelp/sessions/internal/router/rest/internal/docs" + "gitea.dwysokinski.me/twhelp/sessions/internal/router/rest/internal/model" + "github.com/go-chi/chi/v5" + "github.com/go-chi/cors" + httpSwagger "github.com/swaggo/http-swagger" +) + +//go:generate counterfeiter -generate + +type CORSConfig struct { + Enabled bool + AllowedOrigins []string + AllowedMethods []string + AllowCredentials bool + MaxAge int +} + +type SwaggerConfig struct { + Enabled bool + Host string + Schemes []string +} + +//counterfeiter:generate -o internal/mock/api_key_verifier.gen.go . APIKeyVerifier +type APIKeyVerifier interface { + Verify(ctx context.Context, key string) (domain.User, error) +} + +type RouterConfig struct { + APIKeyVerifier APIKeyVerifier + CORS CORSConfig + Swagger SwaggerConfig +} + +func NewRouter(cfg RouterConfig) *chi.Mux { + // handlers + uh := userHandler{} + + router := chi.NewRouter() + + if cfg.CORS.Enabled { + router.Use(cors.Handler(cors.Options{ + AllowedOrigins: cfg.CORS.AllowedOrigins, + AllowCredentials: cfg.CORS.AllowCredentials, + AllowedMethods: cfg.CORS.AllowedMethods, + AllowedHeaders: []string{"Origin", "Content-Length", "Content-Type", "X-API-Key"}, + MaxAge: cfg.CORS.MaxAge, + })) + } + + router.NotFound(routeNotFoundHandler) + router.MethodNotAllowed(methodNotAllowedHandler) + + router.Route("/v1", func(r chi.Router) { + if cfg.Swagger.Enabled { + if cfg.Swagger.Host != "" { + docs.SwaggerInfo.Host = cfg.Swagger.Host + } + if len(cfg.Swagger.Schemes) > 0 { + docs.SwaggerInfo.Schemes = cfg.Swagger.Schemes + } + r.Get("/swagger/*", httpSwagger.Handler()) + } + + authMw := authMiddleware(cfg.APIKeyVerifier) + + r.With(authMw).Get("/user", uh.getAuthenticated) + }) + + return router +} + +func routeNotFoundHandler(w http.ResponseWriter, _ *http.Request) { + renderJSON(w, http.StatusNotFound, model.ErrorResp{ + Error: model.APIError{ + Code: "route-not-found", + Message: "route not found", + }, + }) +} + +func methodNotAllowedHandler(w http.ResponseWriter, _ *http.Request) { + renderJSON(w, http.StatusMethodNotAllowed, model.ErrorResp{ + Error: model.APIError{ + Code: "method-not-allowed", + Message: "method not allowed", + }, + }) +} + +// type internalServerError struct { +// err error +// } +// +// func (e internalServerError) Error() string { +// return e.err.Error() +// } +// +// func (e internalServerError) UserError() string { +// return "internal server error" +// } +// +// func (e internalServerError) Code() domain.ErrorCode { +// return domain.ErrorCodeUnknown +// } +// +// func (e internalServerError) Unwrap() error { +// return e.err +// } +// +// func renderErr(w http.ResponseWriter, err error) { +// var userError domain.Error +// if !errors.As(err, &userError) { +// userError = internalServerError{err: err} +// } +// renderJSON(w, errorCodeToHTTPStatus(userError.Code()), model.ErrorResp{ +// Error: model.APIError{ +// Code: userError.Code().String(), +// Message: userError.UserError(), +// }, +// }) +// } +// +// func errorCodeToHTTPStatus(code domain.ErrorCode) int { +// switch code { +// case domain.ErrorCodeValidationError: +// return http.StatusBadRequest +// case domain.ErrorCodeEntityNotFound: +// return http.StatusNotFound +// case domain.ErrorCodeAlreadyExists: +// return http.StatusConflict +// case domain.ErrorCodeUnknown: +// fallthrough +// default: +// return http.StatusInternalServerError +// } +// } + +func renderJSON(w http.ResponseWriter, status int, data any) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(status) + _ = json.NewEncoder(w).Encode(data) +} diff --git a/internal/router/rest/rest_test.go b/internal/router/rest/rest_test.go new file mode 100644 index 0000000..a2b80fd --- /dev/null +++ b/internal/router/rest/rest_test.go @@ -0,0 +1,113 @@ +package rest_test + +import ( + "encoding/json" + "io" + "net/http" + "net/http/httptest" + "testing" + "time" + + "gitea.dwysokinski.me/twhelp/sessions/internal/router/rest" + "gitea.dwysokinski.me/twhelp/sessions/internal/router/rest/internal/model" + "github.com/go-chi/chi/v5" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/google/uuid" + "github.com/stretchr/testify/assert" +) + +func TestRouteNotFound(t *testing.T) { + t.Parallel() + + expectedResp := &model.ErrorResp{ + Error: model.APIError{ + Code: "route-not-found", + Message: "route not found", + }, + } + + resp := doRequest(rest.NewRouter(rest.RouterConfig{}), http.MethodGet, "/v1/"+uuid.NewString(), "", nil) + defer resp.Body.Close() + assertJSONResponse(t, resp, http.StatusNotFound, expectedResp, &model.ErrorResp{}) +} + +func TestMethodNotAllowed(t *testing.T) { + t.Parallel() + + expectedResp := &model.ErrorResp{ + Error: model.APIError{ + Code: "method-not-allowed", + Message: "method not allowed", + }, + } + + resp := doRequest(rest.NewRouter(rest.RouterConfig{ + Swagger: rest.SwaggerConfig{ + Enabled: true, + }, + }), http.MethodPost, "/v1/swagger/index.html", "", nil) + defer resp.Body.Close() + assertJSONResponse(t, resp, http.StatusMethodNotAllowed, expectedResp, &model.ErrorResp{}) +} + +func TestSwagger(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + target string + expectedContentType string + }{ + { + name: "/v1/swagger/index.html", + target: "/v1/swagger/index.html", + expectedContentType: "text/html; charset=utf-8", + }, + { + name: "/v1/swagger/doc.json", + target: "/v1/swagger/doc.json", + expectedContentType: "application/json; charset=utf-8", + }, + } + router := rest.NewRouter(rest.RouterConfig{ + Swagger: rest.SwaggerConfig{ + Enabled: true, + }, + }) + + for _, tt := range tests { + tt := tt + + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + resp := doRequest(router, http.MethodGet, tt.target, "", nil) + defer resp.Body.Close() + assert.Equal(t, http.StatusOK, resp.StatusCode) + assert.Equal(t, tt.expectedContentType, resp.Header.Get("Content-Type")) + }) + } +} + +func doRequest(mux chi.Router, method, target, apiKey string, body io.Reader) *http.Response { + rr := httptest.NewRecorder() + req := httptest.NewRequest(method, target, body) + if apiKey != "" { + req.Header.Set("X-Api-Key", apiKey) + } + mux.ServeHTTP(rr, req) + return rr.Result() +} + +func assertJSONResponse(tb testing.TB, resp *http.Response, expectedStatus int, expected any, target any) { + tb.Helper() + + assert.Equal(tb, "application/json", resp.Header.Get("Content-Type")) + assert.Equal(tb, expectedStatus, resp.StatusCode) + assert.NoError(tb, json.NewDecoder(resp.Body).Decode(target)) + opts := cmp.Options{ + cmpopts.IgnoreUnexported(time.Time{}), + } + assert.Empty(tb, cmp.Diff(expected, target, opts...)) +} diff --git a/internal/router/rest/user.go b/internal/router/rest/user.go new file mode 100644 index 0000000..fad4857 --- /dev/null +++ b/internal/router/rest/user.go @@ -0,0 +1,25 @@ +package rest + +import ( + "net/http" + + "gitea.dwysokinski.me/twhelp/sessions/internal/router/rest/internal/model" +) + +type userHandler struct { +} + +// @ID getAuthenticatedUser +// @Summary Get the authenticated user +// @Description Get the authenticated user +// @Tags users +// @Produce json +// @Success 200 {object} model.GetUserResp +// @Success 401 {object} model.ErrorResp +// @Failure 500 {object} model.ErrorResp +// @Security ApiKeyAuth +// @Router /user [get] +func (h *userHandler) getAuthenticated(w http.ResponseWriter, r *http.Request) { + u, _ := userFromContext(r.Context()) + renderJSON(w, http.StatusOK, model.NewGetUserResp(u)) +} diff --git a/internal/router/rest/user_test.go b/internal/router/rest/user_test.go new file mode 100644 index 0000000..521ac7a --- /dev/null +++ b/internal/router/rest/user_test.go @@ -0,0 +1,118 @@ +package rest_test + +import ( + "context" + "net/http" + "testing" + "time" + + "gitea.dwysokinski.me/twhelp/sessions/internal/domain" + "gitea.dwysokinski.me/twhelp/sessions/internal/router/rest" + "gitea.dwysokinski.me/twhelp/sessions/internal/router/rest/internal/mock" + "gitea.dwysokinski.me/twhelp/sessions/internal/router/rest/internal/model" + "github.com/google/uuid" +) + +func TestUser_getAuthenticated(t *testing.T) { + t.Parallel() + + now := time.Now() + apiKey := uuid.NewString() + tests := []struct { + name string + setup func(apiKeySvc *mock.FakeAPIKeyVerifier) + apiKey string + expectedStatus int + target any + expectedResponse any + }{ + { + name: "OK", + setup: func(apiKeySvc *mock.FakeAPIKeyVerifier) { + apiKeySvc.VerifyCalls(func(ctx context.Context, key string) (domain.User, error) { + if key != apiKey { + return domain.User{}, domain.APIKeyNotFoundError{ + Key: key, + } + } + + return domain.User{ + ID: 111, + Name: "name", + CreatedAt: now, + }, nil + }) + }, + apiKey: apiKey, + expectedStatus: http.StatusOK, + target: &model.GetUserResp{}, + expectedResponse: &model.GetUserResp{ + Data: model.User{ + ID: 111, + Name: "name", + CreatedAt: now, + }, + }, + }, + { + name: "ERR: apiKey == \"\"", + setup: func(apiKeySvc *mock.FakeAPIKeyVerifier) {}, + apiKey: "", + expectedStatus: http.StatusUnauthorized, + target: &model.ErrorResp{}, + expectedResponse: &model.ErrorResp{ + Error: model.APIError{ + Code: "unauthorized", + Message: "invalid API key", + }, + }, + }, + { + name: "ERR: unexpected API key", + setup: func(apiKeySvc *mock.FakeAPIKeyVerifier) { + apiKeySvc.VerifyCalls(func(ctx context.Context, key string) (domain.User, error) { + if key != apiKey { + return domain.User{}, domain.APIKeyNotFoundError{ + Key: key, + } + } + + return domain.User{ + ID: 111, + Name: "name", + CreatedAt: now, + }, nil + }) + }, + apiKey: uuid.NewString(), + expectedStatus: http.StatusUnauthorized, + target: &model.ErrorResp{}, + expectedResponse: &model.ErrorResp{ + Error: model.APIError{ + Code: "unauthorized", + Message: "invalid API key", + }, + }, + }, + } + + for _, tt := range tests { + tt := tt + + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + apiKeySvc := &mock.FakeAPIKeyVerifier{} + tt.setup(apiKeySvc) + + router := rest.NewRouter(rest.RouterConfig{ + APIKeyVerifier: apiKeySvc, + }) + + resp := doRequest(router, http.MethodGet, "/v1/user", tt.apiKey, nil) + defer resp.Body.Close() + assertJSONResponse(t, resp, tt.expectedStatus, tt.expectedResponse, tt.target) + }) + } + +} diff --git a/internal/service/api_key.go b/internal/service/api_key.go index fa3a927..ecef353 100644 --- a/internal/service/api_key.go +++ b/internal/service/api_key.go @@ -12,6 +12,7 @@ import ( //counterfeiter:generate -o internal/mock/api_key_repository.gen.go . APIKeyRepository type APIKeyRepository interface { Create(ctx context.Context, params domain.CreateAPIKeyParams) (domain.APIKey, error) + Get(ctx context.Context, key string) (domain.APIKey, error) } //counterfeiter:generate -o internal/mock/user_getter.gen.go . UserGetter @@ -54,3 +55,24 @@ func (a *APIKey) Create(ctx context.Context, userID int64) (domain.APIKey, error return apiKey, nil } + +func (a *APIKey) Verify(ctx context.Context, keyStr string) (domain.User, error) { + key, err := uuid.Parse(keyStr) + if err != nil { + return domain.User{}, domain.APIKeyNotFoundError{ + Key: keyStr, + } + } + + ak, err := a.repo.Get(ctx, key.String()) + if err != nil { + return domain.User{}, fmt.Errorf("APIKeyRepository.Get: %w", err) + } + + user, err := a.userSvc.Get(ctx, ak.UserID) + if err != nil { + return domain.User{}, fmt.Errorf("UserService.Get: %w", err) + } + + return user, nil +} diff --git a/internal/service/api_key_test.go b/internal/service/api_key_test.go index 9d5088d..0f1457d 100644 --- a/internal/service/api_key_test.go +++ b/internal/service/api_key_test.go @@ -53,3 +53,45 @@ func TestAPIKey_Create(t *testing.T) { assert.Zero(t, ak) }) } + +func TestAPIKey_Verify(t *testing.T) { + t.Parallel() + + t.Run("OK", func(t *testing.T) { + t.Parallel() + + apiKeyRepo := &mock.FakeAPIKeyRepository{} + apiKeyRepo.GetCalls(func(ctx context.Context, key string) (domain.APIKey, error) { + return domain.APIKey{ + ID: 1, + Key: key, + UserID: 125, + CreatedAt: time.Now(), + }, nil + }) + + userSvc := &mock.FakeUserGetter{} + userSvc.GetCalls(func(ctx context.Context, id int64) (domain.User, error) { + return domain.User{ + ID: id, + Name: uuid.NewString(), + CreatedAt: time.Now(), + }, nil + }) + + user, err := service.NewAPIKey(apiKeyRepo, userSvc).Verify(context.Background(), uuid.NewString()) + assert.NoError(t, err) + assert.Greater(t, user.ID, int64(0)) + }) + + t.Run("ERR: invalid uuid", func(t *testing.T) { + t.Parallel() + + key := "abcd" + user, err := service.NewAPIKey(nil, nil).Verify(context.Background(), key) + assert.ErrorIs(t, err, domain.APIKeyNotFoundError{ + Key: key, + }) + assert.Zero(t, user) + }) +} diff --git a/internal/tools/go.mod b/internal/tools/go.mod index a3f3a79..e52807a 100644 --- a/internal/tools/go.mod +++ b/internal/tools/go.mod @@ -5,6 +5,7 @@ go 1.19 require ( github.com/golangci/golangci-lint v1.50.1 github.com/maxbrunsfeld/counterfeiter/v6 v6.5.0 + github.com/swaggo/swag v1.8.7 ) require ( @@ -15,8 +16,11 @@ require ( github.com/BurntSushi/toml v1.2.1 // indirect github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 // indirect github.com/GaijinEntertainment/go-exhaustruct/v2 v2.3.0 // indirect + github.com/KyleBanks/depth v1.2.1 // indirect github.com/Masterminds/semver v1.5.0 // indirect github.com/OpenPeeDeeP/depguard v1.1.1 // indirect + github.com/PuerkitoBio/purell v1.1.1 // indirect + github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect github.com/alexkohler/prealloc v1.0.0 // indirect github.com/alingse/asasalint v0.0.11 // indirect github.com/ashanbrown/forbidigo v1.3.0 // indirect @@ -31,6 +35,7 @@ require ( github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/charithe/durationcheck v0.0.9 // indirect github.com/chavacava/garif v0.0.0-20220630083739-93517212f375 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/curioswitch/go-reassign v0.2.0 // indirect github.com/daixiang0/gci v0.8.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect @@ -42,7 +47,12 @@ require ( github.com/firefart/nonamedreturns v1.0.4 // indirect github.com/fsnotify/fsnotify v1.5.4 // indirect github.com/fzipp/gocyclo v0.6.0 // indirect + github.com/ghodss/yaml v1.0.0 // indirect github.com/go-critic/go-critic v0.6.5 // indirect + github.com/go-openapi/jsonpointer v0.19.5 // indirect + github.com/go-openapi/jsonreference v0.19.6 // indirect + github.com/go-openapi/spec v0.20.4 // indirect + github.com/go-openapi/swag v0.19.15 // indirect github.com/go-toolsmith/astcast v1.0.0 // indirect github.com/go-toolsmith/astcopy v1.0.2 // indirect github.com/go-toolsmith/astequal v1.0.3 // indirect @@ -78,6 +88,7 @@ require ( github.com/jgautheron/goconst v1.5.1 // indirect github.com/jingyugao/rowserrcheck v1.1.1 // indirect github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af // indirect + github.com/josharian/intern v1.0.0 // indirect github.com/julz/importas v0.1.0 // indirect github.com/kisielk/errcheck v1.6.2 // indirect github.com/kisielk/gotool v1.0.0 // indirect @@ -90,6 +101,7 @@ require ( github.com/leonklingele/grouper v1.1.0 // indirect github.com/lufeee/execinquery v1.2.1 // indirect github.com/magiconair/properties v1.8.6 // indirect + github.com/mailru/easyjson v0.7.6 // indirect github.com/maratori/testableexamples v1.0.0 // indirect github.com/maratori/testpackage v1.1.0 // indirect github.com/matoous/godox v0.0.0-20210227103229-6504466cf951 // indirect @@ -121,6 +133,7 @@ require ( github.com/quasilyte/gogrep v0.0.0-20220828223005-86e4605de09f // indirect github.com/quasilyte/regex/syntax v0.0.0-20200407221936-30656e2c4a95 // indirect github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/ryancurrah/gomodguard v1.2.4 // indirect github.com/ryanrolds/sqlclosecheck v0.3.0 // indirect github.com/sanposhiho/wastedassign/v2 v2.0.6 // indirect @@ -153,6 +166,7 @@ require ( github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect github.com/ultraware/funlen v0.0.3 // indirect github.com/ultraware/whitespace v0.0.5 // indirect + github.com/urfave/cli/v2 v2.3.0 // indirect github.com/uudashr/gocognit v1.0.6 // indirect github.com/yagipy/maintidx v1.0.0 // indirect github.com/yeya24/promlinter v0.2.0 // indirect @@ -163,9 +177,10 @@ require ( golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect golang.org/x/exp/typeparams v0.0.0-20220827204233-334a2380cb91 // indirect golang.org/x/mod v0.6.0 // indirect + golang.org/x/net v0.1.0 // indirect golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde // indirect golang.org/x/sys v0.1.0 // indirect - golang.org/x/text v0.3.8 // indirect + golang.org/x/text v0.4.0 // indirect golang.org/x/tools v0.2.0 // indirect google.golang.org/protobuf v1.28.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect diff --git a/internal/tools/go.sum b/internal/tools/go.sum index ddb6857..582e9ab 100644 --- a/internal/tools/go.sum +++ b/internal/tools/go.sum @@ -52,10 +52,16 @@ github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 h1:sHglBQTwgx+rW github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs= github.com/GaijinEntertainment/go-exhaustruct/v2 v2.3.0 h1:+r1rSv4gvYn0wmRjC8X7IAzX8QezqtFV9m0MUHFJgts= github.com/GaijinEntertainment/go-exhaustruct/v2 v2.3.0/go.mod h1:b3g59n2Y+T5xmcxJL+UEG2f8cQploZm1mR/v6BW0mU0= +github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= +github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/OpenPeeDeeP/depguard v1.1.1 h1:TSUznLjvp/4IUP+OQ0t/4jF4QUyxIcVX8YnghZdunyA= github.com/OpenPeeDeeP/depguard v1.1.1/go.mod h1:JtAMzWkmFEzDPyAd+W0NHl1lvpQKTvT9jnRVsohBKpc= +github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= 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= @@ -100,6 +106,8 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cristalhq/acmd v0.8.1/go.mod h1:LG5oa43pE/BbxtfMoImHCQN++0Su7dzipdgBjMCBVDQ= @@ -133,6 +141,8 @@ github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwV github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-critic/go-critic v0.6.5 h1:fDaR/5GWURljXwF8Eh31T2GZNz9X4jeboS912mWF8Uo= github.com/go-critic/go-critic v0.6.5/go.mod h1:ezfP/Lh7MA6dBNn4c6ab5ALv3sKnZVLx37tr00uuaOY= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= @@ -144,6 +154,16 @@ github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vb 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-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs= +github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= +github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M= +github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM= +github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-toolsmith/astcast v1.0.0 h1:JojxlmI6STnFVG9yOImLeGREv8W2ocNUM+iOhR6jE7g= @@ -294,6 +314,8 @@ github.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+D github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af h1:KA9BjwUk7KlCh6S9EAGWBt1oExIUv9WyNCiRz5amv48= github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af/go.mod h1:HEWGJkRDzjJY2sqdDwxccsGicWEf9BQOZsq2tV+xzM0= github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= +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= @@ -338,6 +360,10 @@ github.com/lufeee/execinquery v1.2.1 h1:hf0Ems4SHcUGBxpGN7Jz78z1ppVkP/837ZlETPCE github.com/lufeee/execinquery v1.2.1/go.mod h1:EC7DrEKView09ocscGHC+apXMIaorh4xqSxS/dy8SbM= 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.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 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.0 h1:GJY4wlzQhuBusMF1oahQCBtUV/AQ/k69IZ68vxaac2Q= @@ -448,6 +474,8 @@ github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 h1:M8mH9eK4OUR4l github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567/go.mod h1:DWNGW8A4Y+GyBgPuaQJuWiy0XYftx4Xm/y5Jqk9I6VQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryancurrah/gomodguard v1.2.4 h1:CpMSDKan0LtNGGhPrvupAoLeObRFjND8/tU1rEOtBp4= github.com/ryancurrah/gomodguard v1.2.4/go.mod h1:+Kem4VjWwvFpUJRJSwa16s1tBJe+vbv02+naTow2f6M= @@ -466,6 +494,7 @@ github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c h1:W65qqJCIOVP4jpqP github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c/go.mod h1:/PevMnwAxekIXwN8qQyfc5gl2NlkB3CQlkizAbOkeBs= 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/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 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= @@ -506,12 +535,15 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf 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.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 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 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 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/swaggo/swag v1.8.7 h1:2K9ivTD3teEO+2fXV6zrZKDqk5IuU2aJtBDo8U7omWU= +github.com/swaggo/swag v1.8.7/go.mod h1:ezQVUUhly8dludpVk+/PuwJWvLLanB13ygV5Pr9enSk= github.com/tdakkota/asciicheck v0.1.1 h1:PKzG7JUTUmVspQTDqtkX9eSiLGossXTybutHwTXuO0A= github.com/tdakkota/asciicheck v0.1.1/go.mod h1:yHp0ai0Z9gUljN3o0xMhYJnH/IcvkdTBOX2fmJ93JEM= github.com/tenntenn/modver v1.0.1 h1:2klLppGhDgzJrScMpkj9Ujy3rXPUspSjAcev9tSEBgA= @@ -532,6 +564,8 @@ github.com/ultraware/funlen v0.0.3 h1:5ylVWm8wsNwH5aWo9438pwvsK0QiqVuUrt9bn7S/iL github.com/ultraware/funlen v0.0.3/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA= github.com/ultraware/whitespace v0.0.5 h1:hh+/cpIcopyMYbZNVov9iSxvJU3OYQg78Sfaqzi/CzI= github.com/ultraware/whitespace v0.0.5/go.mod h1:aVMh/gQve5Maj9hQ/hg+F75lr/X5A89uZnzAmWSineA= +github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M= +github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/uudashr/gocognit v1.0.6 h1:2Cgi6MweCsdB6kpcVQp7EW4U23iBFQWfTXiWlyp842Y= github.com/uudashr/gocognit v1.0.6/go.mod h1:nAIUuVBnYU7pcninia3BHOvQkpQCeO76Uscky5BOwcY= github.com/yagipy/maintidx v1.0.0 h1:h5NvIsCz+nRDapQ0exNv4aJ0yXSI0420omVANTv3GJM= @@ -646,10 +680,12 @@ golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 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-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= 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-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= 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= @@ -715,6 +751,7 @@ golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -742,8 +779,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/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.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY= -golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 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= @@ -935,6 +972,7 @@ 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/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.3/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.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -942,6 +980,7 @@ 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-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/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= diff --git a/internal/tools/tools.go b/internal/tools/tools.go index 17bfabd..086b9d1 100644 --- a/internal/tools/tools.go +++ b/internal/tools/tools.go @@ -6,4 +6,5 @@ package tools import ( _ "github.com/golangci/golangci-lint/cmd/golangci-lint" _ "github.com/maxbrunsfeld/counterfeiter/v6" + _ "github.com/swaggo/swag/cmd/swag" ) diff --git a/k8s/base/api.yml b/k8s/base/api.yml index cc525f3..119b284 100644 --- a/k8s/base/api.yml +++ b/k8s/base/api.yml @@ -30,6 +30,8 @@ spec: value: "10" - name: DB_MAX_IDLE_CONNECTIONS value: "5" + - name: API_SWAGGER_ENABLED + value: "true" livenessProbe: httpGet: path: /_meta/livez