diff --git a/Makefile b/Makefile index 4033ecb..d996890 100644 --- a/Makefile +++ b/Makefile @@ -29,6 +29,11 @@ install-goose: @echo "Installing github.com/pressly/goose..." @(test -f $(GOOSE_PATH) && echo "github.com/pressly/goose is already installed. Skipping...") || (wget -q -O $(GOOSE_PATH) https://github.com/pressly/goose/releases/download/v3.20.0/goose_$(GOOS)_$(OSARCH) && chmod u+x $(GOOSE_PATH)) +.PHONY: install-oapi-codegen +install-oapi-codegen: + @echo "Installing github.com/deepmap/oapi-codegen..." + @go install github.com/deepmap/oapi-codegen/v2/cmd/oapi-codegen@v2.1.0 + .PHONY: install-tools install-tools: install-golangci-lint install-counterfeiter install-goose @@ -37,7 +42,7 @@ install-tools: install-golangci-lint install-counterfeiter install-goose install: install-tools install-git-hooks .PHONY: generate -generate: install-counterfeiter +generate: install-counterfeiter install-oapi-codegen go generate ./... .PHONY: create-sql-migration diff --git a/cmd/dcbot/internal/twhelp.go b/cmd/dcbot/internal/twhelp.go index bedf543..8cc56c7 100644 --- a/cmd/dcbot/internal/twhelp.go +++ b/cmd/dcbot/internal/twhelp.go @@ -16,7 +16,7 @@ type twhelpClientConfig struct { URL *url.URL `envconfig:"URL" required:"true"` Timeout time.Duration `envconfig:"TIMEOUT" default:"10s"` RateLimiterEnabled bool `envconfig:"RATE_LIMITER_ENABLED" default:"false"` - RateLimiterMaxRequestsPerSecond float64 `envconfig:"RATE_LIMITER_MAX_REQUESTS_PER_SECOND" default:"10"` + RateLimiterMaxRequestsPerSecond float64 `envconfig:"RATE_LIMITER_MAX_REQUESTS_PER_SECOND" default:"50"` RateLimiterRequestBurst int `envconfig:"RATE_LIMITER_REQUEST_BURST" default:"5"` } @@ -27,6 +27,7 @@ func NewTWHelpService(version string) (*adapter.TWHelpHTTP, error) { } opts := []twhelp.ClientOption{ + twhelp.WithBaseURL(cfg.URL.String()), twhelp.WithHTTPClient(&http.Client{ Timeout: cfg.Timeout, }), @@ -37,5 +38,10 @@ func NewTWHelpService(version string) (*adapter.TWHelpHTTP, error) { opts = append(opts, twhelp.WithRateLimiter(rate.NewLimiter(rate.Limit(cfg.RateLimiterMaxRequestsPerSecond), cfg.RateLimiterRequestBurst))) } - return adapter.NewTWHelpHTTP(twhelp.NewClient(cfg.URL, opts...)), nil + client, err := twhelp.NewClientWithResponses("", opts...) + if err != nil { + return nil, err + } + + return adapter.NewTWHelpHTTP(client), nil } diff --git a/go.mod b/go.mod index 68d3bfc..77d86a7 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa github.com/kelseyhightower/envconfig v1.4.0 github.com/nicksnyder/go-i18n/v2 v2.4.0 + github.com/oapi-codegen/runtime v1.1.1 github.com/ory/dockertest/v3 v3.10.0 github.com/pressly/goose/v3 v3.20.0 github.com/redis/go-redis/v9 v9.5.1 @@ -29,6 +30,7 @@ require ( github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect + github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/containerd/continuity v0.4.2 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect diff --git a/go.sum b/go.sum index 7c40d74..87a1df2 100644 --- a/go.sum +++ b/go.sum @@ -2,13 +2,16 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= 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 v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= +github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= +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/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= @@ -60,6 +63,7 @@ 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/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= 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= @@ -78,12 +82,12 @@ 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/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= -github.com/nicksnyder/go-i18n/v2 v2.2.2 h1:Iv/FL6pvYmDqybEZkr4TrOv8jSHezwpE77K68kcaft8= -github.com/nicksnyder/go-i18n/v2 v2.2.2/go.mod h1:fF2++lPHlo+/kPaj3nB0uxtPwzlPm+BlgwGX7MkeGj0= github.com/nicksnyder/go-i18n/v2 v2.4.0 h1:3IcvPOAvnCKwNm0TB0dLDTuawWEj+ax/RERNC+diLMM= github.com/nicksnyder/go-i18n/v2 v2.4.0/go.mod h1:nxYSZE9M0bf3Y70gPQjN9ha7XNHX7gMc814+6wVyEI4= 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/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/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-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI= @@ -110,6 +114,7 @@ github.com/sethvargo/go-retry v0.2.4 h1:T+jHEQy/zKJf5s95UkguisicE0zuF9y7+/vgz08O github.com/sethvargo/go-retry v0.2.4/go.mod h1:1afjQuvh7s4gflMObvjLPaWgluLLyhA1wmVZ6KLpICw= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -142,7 +147,6 @@ github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 h1:+qGGcbkzsfDQNPPe github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913/go.mod h1:4aEEwZQutDLsQv2Deui4iYQ6DWTxR14g6m8Wv88+Xqk= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 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= @@ -153,12 +157,10 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk 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-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= 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/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -166,30 +168,22 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL 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-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -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= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-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-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/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.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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/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.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.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= @@ -198,7 +192,6 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm 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/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -208,7 +201,6 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8T gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/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.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= diff --git a/internal/adapter/group_bun_repository.go b/internal/adapter/group_bun_repository.go index f721f44..3583cf3 100644 --- a/internal/adapter/group_bun_repository.go +++ b/internal/adapter/group_bun_repository.go @@ -71,7 +71,7 @@ func (g *GroupBunRepository) Update(ctx context.Context, id string, params domai return g.Get(ctx, id) } -func (g *GroupBunRepository) AddMonitor(ctx context.Context, id string, tribeID int64) (domain.GroupWithMonitors, error) { +func (g *GroupBunRepository) AddMonitor(ctx context.Context, id string, tribeID int) (domain.GroupWithMonitors, error) { parsedID, err := uuid.Parse(id) if err != nil { return domain.GroupWithMonitors{}, domain.GroupDoesNotExistError{ID: id} @@ -287,7 +287,7 @@ func (l listGroupsParamsApplier) apply(q *bun.SelectQuery) *bun.SelectQuery { return q } -func mapAddMonitorError(err error, groupID string, tribeID int64) error { +func mapAddMonitorError(err error, groupID string, tribeID int) error { var pgError pgdriver.Error if !errors.As(err, &pgError) { return err diff --git a/internal/adapter/group_bun_repository_test.go b/internal/adapter/group_bun_repository_test.go index 70953ef..2b82f4e 100644 --- a/internal/adapter/group_bun_repository_test.go +++ b/internal/adapter/group_bun_repository_test.go @@ -158,7 +158,7 @@ func TestGroupBunRepository_AddMonitor(t *testing.T) { t.Run("OK", func(t *testing.T) { t.Parallel() - var tribeID int64 = 91283718 + var tribeID int = 91283718 updatedGroup, err := repo.AddMonitor(context.Background(), group.ID, tribeID) assert.NoError(t, err) diff --git a/internal/adapter/internal/bunmodel/monitor.go b/internal/adapter/internal/bunmodel/monitor.go index 5843e43..05eb2d7 100644 --- a/internal/adapter/internal/bunmodel/monitor.go +++ b/internal/adapter/internal/bunmodel/monitor.go @@ -13,7 +13,7 @@ type Monitor struct { ID uuid.UUID `bun:"id,pk,nullzero"` GroupID uuid.UUID `bun:"group_id"` - TribeID int64 `bun:"tribe_id,nullzero"` + TribeID int `bun:"tribe_id,nullzero"` CreatedAt time.Time `bun:"created_at,nullzero"` } diff --git a/internal/adapter/twhelp_http.go b/internal/adapter/twhelp_http.go index 52314a2..35e1c69 100644 --- a/internal/adapter/twhelp_http.go +++ b/internal/adapter/twhelp_http.go @@ -3,6 +3,8 @@ package adapter import ( "context" "errors" + "slices" + "strings" "time" "gitea.dwysokinski.me/twhelp/dcbot/internal/domain" @@ -10,57 +12,57 @@ import ( ) type TWHelpHTTP struct { - client *twhelp.Client + client *twhelp.ClientWithResponses } -func NewTWHelpHTTP(client *twhelp.Client) *TWHelpHTTP { +func NewTWHelpHTTP(client *twhelp.ClientWithResponses) *TWHelpHTTP { return &TWHelpHTTP{client: client} } func (t *TWHelpHTTP) ListVersions(ctx context.Context) ([]domain.TWVersion, error) { - versions, err := t.client.ListVersions(ctx) + resp, err := t.client.ListVersionsWithResponse(ctx, nil) if err != nil { return nil, err } + if resp.JSONDefault != nil { + return nil, t.convertErrors(resp.JSONDefault.Errors) + } - res := make([]domain.TWVersion, 0, len(versions)) - for _, v := range versions { + res := make([]domain.TWVersion, 0, len(resp.JSON200.Data)) + for _, v := range resp.JSON200.Data { res = append(res, t.convertVersionToDomain(v)) } return res, nil } -func (t *TWHelpHTTP) ListOpenServers(ctx context.Context, version string, offset, limit int32) ([]domain.TWServer, error) { - return t.listServers(ctx, version, twhelp.ListServersQueryParams{ - Limit: limit, - Offset: offset, - Open: twhelp.NullBool{ - Bool: true, - Valid: true, - }, +func (t *TWHelpHTTP) ListOpenServers(ctx context.Context, versionCode string, limit int) ([]domain.TWServer, error) { + open := true + return t.listServers(ctx, versionCode, &twhelp.ListServersParams{ + Limit: &limit, + Open: &open, }) } -func (t *TWHelpHTTP) ListClosedServers(ctx context.Context, version string, offset, limit int32) ([]domain.TWServer, error) { - return t.listServers(ctx, version, twhelp.ListServersQueryParams{ - Limit: limit, - Offset: offset, - Open: twhelp.NullBool{ - Bool: false, - Valid: true, - }, +func (t *TWHelpHTTP) ListClosedServers(ctx context.Context, versionCode string, limit int) ([]domain.TWServer, error) { + open := false + return t.listServers(ctx, versionCode, &twhelp.ListServersParams{ + Limit: &limit, + Open: &open, }) } -func (t *TWHelpHTTP) listServers(ctx context.Context, version string, params twhelp.ListServersQueryParams) ([]domain.TWServer, error) { - servers, err := t.client.ListServers(ctx, version, params) +func (t *TWHelpHTTP) listServers(ctx context.Context, versionCode string, params *twhelp.ListServersParams) ([]domain.TWServer, error) { + resp, err := t.client.ListServersWithResponse(ctx, versionCode, params) if err != nil { return nil, err } + if resp.JSONDefault != nil { + return nil, t.convertErrors(resp.JSONDefault.Errors) + } - res := make([]domain.TWServer, 0, len(servers)) - for _, s := range servers { + res := make([]domain.TWServer, 0, len(resp.JSON200.Data)) + for _, s := range resp.JSON200.Data { res = append(res, t.convertServerToDomain(s)) } @@ -72,60 +74,78 @@ func (t *TWHelpHTTP) GetOpenServer(ctx context.Context, versionCode, serverKey s if err != nil { return domain.TWServer{}, err } + if !server.Open { return domain.TWServer{}, domain.TWServerNotFoundError{ VersionCode: versionCode, Key: serverKey, } } + return server, nil } func (t *TWHelpHTTP) GetServer(ctx context.Context, versionCode, serverKey string) (domain.TWServer, error) { - server, err := t.client.GetServer(ctx, versionCode, serverKey) + resp, err := t.client.GetServerWithResponse(ctx, versionCode, serverKey) if err != nil { - var apiErr twhelp.APIError - if !errors.As(err, &apiErr) || apiErr.Code != twhelp.ErrorCodeEntityNotFound { - return domain.TWServer{}, err - } - return domain.TWServer{}, domain.TWServerNotFoundError{ - VersionCode: versionCode, - Key: serverKey, - } + return domain.TWServer{}, err } - return t.convertServerToDomain(server), nil + if resp.JSONDefault != nil { + if slices.ContainsFunc(resp.JSONDefault.Errors, func(err twhelp.Error) bool { + return err.Code == twhelp.ErrorCodeServerNotFound + }) { + return domain.TWServer{}, domain.TWServerNotFoundError{ + VersionCode: versionCode, + Key: serverKey, + } + } + + return domain.TWServer{}, t.convertErrors(resp.JSONDefault.Errors) + } + + return t.convertServerToDomain(resp.JSON200.Data), nil } -func (t *TWHelpHTTP) GetTribeByID(ctx context.Context, versionCode, serverKey string, id int64) (domain.Tribe, error) { - tribe, err := t.client.GetTribeByID(ctx, versionCode, serverKey, id) +func (t *TWHelpHTTP) GetTribeByID(ctx context.Context, versionCode, serverKey string, id int) (domain.Tribe, error) { + resp, err := t.client.GetTribeWithResponse(ctx, versionCode, serverKey, id) if err != nil { - var apiErr twhelp.APIError - if !errors.As(err, &apiErr) || apiErr.Code != twhelp.ErrorCodeEntityNotFound { - return domain.Tribe{}, err - } - return domain.Tribe{}, domain.TribeIDNotFoundError{ - VersionCode: versionCode, - ServerKey: serverKey, - ID: id, - } + return domain.Tribe{}, err } - return t.convertTribeToDomain(tribe), nil + if resp.JSONDefault != nil { + if slices.ContainsFunc(resp.JSONDefault.Errors, func(err twhelp.Error) bool { + return err.Code == twhelp.ErrorCodeTribeNotFound + }) { + return domain.Tribe{}, domain.TribeIDNotFoundError{ + VersionCode: versionCode, + ServerKey: serverKey, + ID: id, + } + } + + return domain.Tribe{}, t.convertErrors(resp.JSONDefault.Errors) + } + + return t.convertTribeToDomain(resp.JSON200.Data), nil } func (t *TWHelpHTTP) GetExistingTribeByTag(ctx context.Context, versionCode, serverKey, tribeTag string) (domain.Tribe, error) { - tribes, err := t.client.ListTribes(ctx, versionCode, serverKey, twhelp.ListTribesQueryParams{ - Limit: 1, - Tags: []string{tribeTag}, - Deleted: twhelp.NullBool{ - Valid: true, - Bool: false, - }, + deleted := false + tags := []string{tribeTag} + limit := 1 + + resp, err := t.client.ListTribesWithResponse(ctx, versionCode, serverKey, &twhelp.ListTribesParams{ + Deleted: &deleted, + Tag: &tags, + Limit: &limit, }) if err != nil { return domain.Tribe{}, err } + if resp.JSONDefault != nil { + return domain.Tribe{}, t.convertErrors(resp.JSONDefault.Errors) + } - if len(tribes) == 0 { + if len(resp.JSON200.Data) == 0 { return domain.Tribe{}, domain.TribeTagNotFoundError{ VersionCode: versionCode, ServerKey: serverKey, @@ -133,30 +153,23 @@ func (t *TWHelpHTTP) GetExistingTribeByTag(ctx context.Context, versionCode, ser } } - return t.convertTribeToDomain(tribes[0]), nil + return t.convertTribeToDomain(resp.JSON200.Data[0]), nil } -func (t *TWHelpHTTP) ListTribesByTag( - ctx context.Context, - versionCode, serverKey string, - tribeTags []string, - offset, limit int32, -) ([]domain.Tribe, error) { - tribes, err := t.client.ListTribes(ctx, versionCode, serverKey, twhelp.ListTribesQueryParams{ - Limit: limit, - Offset: offset, - Tags: tribeTags, - Deleted: twhelp.NullBool{ - Valid: true, - Bool: false, - }, +func (t *TWHelpHTTP) ListTribesByTag(ctx context.Context, versionCode, serverKey string, tribeTags []string, limit int) ([]domain.Tribe, error) { + resp, err := t.client.ListTribesWithResponse(ctx, versionCode, serverKey, &twhelp.ListTribesParams{ + Tag: &tribeTags, + Limit: &limit, }) if err != nil { return nil, err } + if resp.JSONDefault != nil { + return nil, t.convertErrors(resp.JSONDefault.Errors) + } - res := make([]domain.Tribe, 0, len(tribes)) - for _, tr := range tribes { + res := make([]domain.Tribe, 0, len(resp.JSON200.Data)) + for _, tr := range resp.JSON200.Data { res = append(res, t.convertTribeToDomain(tr)) } @@ -167,19 +180,21 @@ func (t *TWHelpHTTP) ListVillagesByCoords( ctx context.Context, versionCode, serverKey string, coords []string, - offset, limit int32, + limit int, ) ([]domain.Village, error) { - villages, err := t.client.ListVillages(ctx, versionCode, serverKey, twhelp.ListVillagesQueryParams{ - Limit: limit, - Offset: offset, - Coords: coords, + resp, err := t.client.ListVillagesWithResponse(ctx, versionCode, serverKey, &twhelp.ListVillagesParams{ + Limit: &limit, + Coords: &coords, }) if err != nil { return nil, err } + if resp.JSONDefault != nil { + return nil, t.convertErrors(resp.JSONDefault.Errors) + } - res := make([]domain.Village, 0, len(villages)) - for _, v := range villages { + res := make([]domain.Village, 0, len(resp.JSON200.Data)) + for _, v := range resp.JSON200.Data { res = append(res, t.convertVillageToDomain(v)) } @@ -190,22 +205,26 @@ func (t *TWHelpHTTP) ListEnnoblementsSince( ctx context.Context, versionCode, serverKey string, since time.Time, - offset, limit int32, + limit int, ) ([]domain.Ennoblement, error) { - ennoblements, err := t.client.ListEnnoblements(ctx, versionCode, serverKey, twhelp.ListEnnoblementsQueryParams{ - Limit: limit, - Offset: offset, - Since: since, - Sort: []twhelp.EnnoblementSort{ - twhelp.EnnoblementSortCreatedAtASC, - }, + sort := []string{ + string(twhelp.ListEnnoblementsParamsSortCreatedAtASC), + } + + resp, err := t.client.ListEnnoblementsWithResponse(ctx, versionCode, serverKey, &twhelp.ListEnnoblementsParams{ + Limit: &limit, + Sort: &sort, + Since: &since, }) if err != nil { return nil, err } + if resp.JSONDefault != nil { + return nil, t.convertErrors(resp.JSONDefault.Errors) + } - res := make([]domain.Ennoblement, 0, len(ennoblements)) - for _, e := range ennoblements { + res := make([]domain.Ennoblement, 0, len(resp.JSON200.Data)) + for _, e := range resp.JSON200.Data { res = append(res, t.convertEnnoblementToDomain(e)) } @@ -217,45 +236,71 @@ func (t *TWHelpHTTP) convertVersionToDomain(v twhelp.Version) domain.TWVersion { } func (t *TWHelpHTTP) convertServerToDomain(s twhelp.Server) domain.TWServer { - return domain.TWServer(s) + return domain.TWServer{ + Key: s.Key, + URL: s.Url, + Open: s.Open, + } } func (t *TWHelpHTTP) convertTribeToDomain(tr twhelp.Tribe) domain.Tribe { - return domain.Tribe(tr) + var deletedAt time.Time + if tr.DeletedAt != nil { + deletedAt = *tr.DeletedAt + } + + return domain.Tribe{ + ID: tr.Id, + Tag: tr.Tag, + Name: tr.Name, + ProfileURL: tr.ProfileUrl, + DeletedAt: deletedAt, + } } func (t *TWHelpHTTP) convertTribeMetaToDomain(tr twhelp.TribeMeta) domain.TribeMeta { - return domain.TribeMeta(tr) + return domain.TribeMeta{ + ID: tr.Id, + Name: tr.Name, + Tag: tr.Tag, + ProfileURL: tr.ProfileUrl, + } } -func (t *TWHelpHTTP) convertNullTribeMetaToDomain(tr twhelp.NullTribeMeta) domain.NullTribeMeta { - return domain.NullTribeMeta{ - Tribe: t.convertTribeMetaToDomain(tr.Tribe), - Valid: tr.Valid, +func (t *TWHelpHTTP) convertNullTribeMetaToDomain(tr *twhelp.NullTribeMeta) domain.NullTribeMeta { + res := domain.NullTribeMeta{ + Valid: tr != nil, } + if tr != nil { + res.Tribe = t.convertTribeMetaToDomain(*tr) + } + return res } func (t *TWHelpHTTP) convertPlayerMetaToDomain(p twhelp.PlayerMeta) domain.PlayerMeta { return domain.PlayerMeta{ - ID: p.ID, + ID: p.Id, Name: p.Name, - ProfileURL: p.ProfileURL, + ProfileURL: p.ProfileUrl, Tribe: t.convertNullTribeMetaToDomain(p.Tribe), } } -func (t *TWHelpHTTP) convertNullPlayerMetaToDomain(p twhelp.NullPlayerMeta) domain.NullPlayerMeta { - return domain.NullPlayerMeta{ - Player: t.convertPlayerMetaToDomain(p.Player), - Valid: p.Valid, +func (t *TWHelpHTTP) convertNullPlayerMetaToDomain(p *twhelp.NullPlayerMeta) domain.NullPlayerMeta { + res := domain.NullPlayerMeta{ + Valid: p != nil, } + if p != nil { + res.Player = t.convertPlayerMetaToDomain(*p) + } + return res } func (t *TWHelpHTTP) convertVillageToDomain(v twhelp.Village) domain.Village { return domain.Village{ - ID: v.ID, + ID: v.Id, FullName: v.FullName, - ProfileURL: v.ProfileURL, + ProfileURL: v.ProfileUrl, Points: v.Points, X: v.X, Y: v.Y, @@ -265,18 +310,43 @@ func (t *TWHelpHTTP) convertVillageToDomain(v twhelp.Village) domain.Village { func (t *TWHelpHTTP) convertVillageMetaToDomain(v twhelp.VillageMeta) domain.VillageMeta { return domain.VillageMeta{ - ID: v.ID, + ID: v.Id, FullName: v.FullName, - ProfileURL: v.ProfileURL, + ProfileURL: v.ProfileUrl, Player: t.convertNullPlayerMetaToDomain(v.Player), } } func (t *TWHelpHTTP) convertEnnoblementToDomain(e twhelp.Ennoblement) domain.Ennoblement { return domain.Ennoblement{ - ID: e.ID, + ID: e.Id, Village: t.convertVillageMetaToDomain(e.Village), - NewOwner: t.convertNullPlayerMetaToDomain(e.NewOwner), + NewOwner: t.convertPlayerMetaToDomain(e.NewOwner), CreatedAt: e.CreatedAt, } } + +func (t *TWHelpHTTP) convertErrors(errs []twhelp.Error) error { + if len(errs) == 0 { + return nil + } + + sep := "\n" + + n := len(sep) * (len(errs) - 1) + for _, err := range errs { + n += len(err.Message) + } + + var b strings.Builder + b.Grow(n) + + for i, err := range errs { + if i > 0 { + b.WriteString(sep) + } + b.WriteString(err.Message) + } + + return errors.New(b.String()) +} diff --git a/internal/adapter/village_redis_repository.go b/internal/adapter/village_redis_repository.go index 38bf52a..f0321e0 100644 --- a/internal/adapter/village_redis_repository.go +++ b/internal/adapter/village_redis_repository.go @@ -26,8 +26,8 @@ type translateVillageCoordsParamsRedis struct { VersionCode string ServerKey string Coords []string - PerPage int32 - MaxPage int32 + PerPage int + MaxPage int SHA256 string } diff --git a/internal/discord/bot.go b/internal/discord/bot.go index d3be39c..18136f5 100644 --- a/internal/discord/bot.go +++ b/internal/discord/bot.go @@ -34,12 +34,8 @@ type ChoiceService interface { } type VillageService interface { - TranslateCoords( - ctx context.Context, - params domain.TranslateVillageCoordsParams, - page int32, - ) (domain.TranslateVillageCoordsResult, error) - TranslateCoordsFromHash(ctx context.Context, paramsSHA256Hash string, page int32) (domain.TranslateVillageCoordsResult, error) + TranslateCoords(ctx context.Context, params domain.TranslateVillageCoordsParams, page int) (domain.TranslateVillageCoordsResult, error) + TranslateCoordsFromHash(ctx context.Context, paramsSHA256Hash string, page int) (domain.TranslateVillageCoordsResult, error) } type Bot struct { diff --git a/internal/discord/command_coords.go b/internal/discord/command_coords.go index 852fb20..088a1be 100644 --- a/internal/discord/command_coords.go +++ b/internal/discord/command_coords.go @@ -358,20 +358,20 @@ const ( coordsTranslationButtonIDSeparator = "-" ) -func (c *coordsCommand) buildButtonID(page int32, hash string) string { +func (c *coordsCommand) buildButtonID(page int, hash string) string { return fmt.Sprintf("%s%s%d%s%s", coordsTranslationButtonIDPrefix, coordsTranslationButtonIDSeparator, page, coordsTranslationButtonIDSeparator, hash) } -func (c *coordsCommand) parseButtonID(id string) (int32, string, error) { +func (c *coordsCommand) parseButtonID(id string) (int, string, error) { chunks := strings.Split(id, coordsTranslationButtonIDSeparator) if len(chunks) != 3 || chunks[0] != coordsTranslationButtonIDPrefix { return 0, "", fmt.Errorf("couldn't parse '%s': incorrect format", id) } - page, err := strconv.ParseInt(chunks[1], 10, 32) + page, err := strconv.Atoi(id) if err != nil { return 0, "", fmt.Errorf("couldn't parse '%s': %w", id, err) } - return int32(page), chunks[2], nil + return page, chunks[2], nil } diff --git a/internal/domain/ennoblement.go b/internal/domain/ennoblement.go index d8f7e08..f84b99f 100644 --- a/internal/domain/ennoblement.go +++ b/internal/domain/ennoblement.go @@ -3,9 +3,9 @@ package domain import "time" type Ennoblement struct { - ID int64 + ID int Village VillageMeta - NewOwner NullPlayerMeta + NewOwner PlayerMeta CreatedAt time.Time } @@ -14,5 +14,5 @@ func (e Ennoblement) IsBarbarian() bool { } func (e Ennoblement) IsSelfConquer() bool { - return e.NewOwner.Player.ID == e.Village.Player.Player.ID + return e.NewOwner.ID == e.Village.Player.Player.ID } diff --git a/internal/domain/ennoblement_notification_test.go b/internal/domain/ennoblement_notification_test.go index 48a647a..ca79062 100644 --- a/internal/domain/ennoblement_notification_test.go +++ b/internal/domain/ennoblement_notification_test.go @@ -35,17 +35,14 @@ func TestNewEnnoblementNotifications(t *testing.T) { }, ennoblements: []domain.Ennoblement{ { - NewOwner: domain.NullPlayerMeta{ - Player: domain.PlayerMeta{ - ID: 125, - Tribe: domain.NullTribeMeta{ - Tribe: domain.TribeMeta{ - ID: 150, - }, - Valid: true, + NewOwner: domain.PlayerMeta{ + ID: 125, + Tribe: domain.NullTribeMeta{ + Tribe: domain.TribeMeta{ + ID: 150, }, + Valid: true, }, - Valid: true, }, Village: domain.VillageMeta{ Player: domain.NullPlayerMeta{ @@ -63,31 +60,25 @@ func TestNewEnnoblementNotifications(t *testing.T) { }, }, { // Barbarians == false - NewOwner: domain.NullPlayerMeta{ - Player: domain.PlayerMeta{ - ID: 125, - Tribe: domain.NullTribeMeta{ - Tribe: domain.TribeMeta{ - ID: 150, - }, - Valid: true, + NewOwner: domain.PlayerMeta{ + ID: 125, + Tribe: domain.NullTribeMeta{ + Tribe: domain.TribeMeta{ + ID: 150, }, + Valid: true, }, - Valid: true, }, }, { // Internals == false - NewOwner: domain.NullPlayerMeta{ - Player: domain.PlayerMeta{ - ID: 125, - Tribe: domain.NullTribeMeta{ - Tribe: domain.TribeMeta{ - ID: 150, - }, - Valid: true, + NewOwner: domain.PlayerMeta{ + ID: 125, + Tribe: domain.NullTribeMeta{ + Tribe: domain.TribeMeta{ + ID: 150, }, + Valid: true, }, - Valid: true, }, Village: domain.VillageMeta{ Player: domain.NullPlayerMeta{ @@ -105,17 +96,14 @@ func TestNewEnnoblementNotifications(t *testing.T) { }, }, { - NewOwner: domain.NullPlayerMeta{ - Player: domain.PlayerMeta{ - ID: 125, - Tribe: domain.NullTribeMeta{ - Tribe: domain.TribeMeta{ - ID: 153, - }, - Valid: true, + NewOwner: domain.PlayerMeta{ + ID: 125, + Tribe: domain.NullTribeMeta{ + Tribe: domain.TribeMeta{ + ID: 153, }, + Valid: true, }, - Valid: true, }, Village: domain.VillageMeta{ Player: domain.NullPlayerMeta{ diff --git a/internal/domain/ennoblement_test.go b/internal/domain/ennoblement_test.go index cc4e793..78b18d8 100644 --- a/internal/domain/ennoblement_test.go +++ b/internal/domain/ennoblement_test.go @@ -11,20 +11,14 @@ func TestEnnoblement_IsBarbarian(t *testing.T) { t.Parallel() assert.True(t, domain.Ennoblement{ - NewOwner: domain.NullPlayerMeta{ - Player: domain.PlayerMeta{ - ID: 123, - }, - Valid: true, + NewOwner: domain.PlayerMeta{ + ID: 123, }, }.IsBarbarian()) assert.False(t, domain.Ennoblement{ - NewOwner: domain.NullPlayerMeta{ - Player: domain.PlayerMeta{ - ID: 123, - }, - Valid: true, + NewOwner: domain.PlayerMeta{ + ID: 123, }, Village: domain.VillageMeta{ Player: domain.NullPlayerMeta{ @@ -41,11 +35,8 @@ func TestEnnoblement_IsSelfConquer(t *testing.T) { t.Parallel() assert.True(t, domain.Ennoblement{ - NewOwner: domain.NullPlayerMeta{ - Player: domain.PlayerMeta{ - ID: 123, - }, - Valid: true, + NewOwner: domain.PlayerMeta{ + ID: 123, }, Village: domain.VillageMeta{ Player: domain.NullPlayerMeta{ @@ -58,20 +49,14 @@ func TestEnnoblement_IsSelfConquer(t *testing.T) { }.IsSelfConquer()) assert.False(t, domain.Ennoblement{ - NewOwner: domain.NullPlayerMeta{ - Player: domain.PlayerMeta{ - ID: 123, - }, - Valid: true, + NewOwner: domain.PlayerMeta{ + ID: 123, }, }.IsSelfConquer()) assert.False(t, domain.Ennoblement{ - NewOwner: domain.NullPlayerMeta{ - Player: domain.PlayerMeta{ - ID: 123, - }, - Valid: true, + NewOwner: domain.PlayerMeta{ + ID: 123, }, Village: domain.VillageMeta{ Player: domain.NullPlayerMeta{ diff --git a/internal/domain/group.go b/internal/domain/group.go index d00750e..563bbc8 100644 --- a/internal/domain/group.go +++ b/internal/domain/group.go @@ -48,7 +48,7 @@ func (g GroupWithMonitors) IsGain(e Ennoblement) bool { var n bool for _, m := range g.Monitors { - if m.TribeID == e.NewOwner.Player.Tribe.Tribe.ID { + if m.TribeID == e.NewOwner.Tribe.Tribe.ID { n = true break } @@ -61,7 +61,7 @@ func (g GroupWithMonitors) IsInternal(e Ennoblement) bool { var n, o bool for _, m := range g.Monitors { - if m.TribeID == e.NewOwner.Player.Tribe.Tribe.ID { + if m.TribeID == e.NewOwner.Tribe.Tribe.ID { n = true } diff --git a/internal/domain/group_test.go b/internal/domain/group_test.go index b1d7be3..2a706c7 100644 --- a/internal/domain/group_test.go +++ b/internal/domain/group_test.go @@ -25,17 +25,14 @@ func TestGroupWithMonitors_CanSendEnnoblementNotificationTypeGain(t *testing.T) }, }, }.CanSendEnnoblementNotificationTypeGain(domain.Ennoblement{ - NewOwner: domain.NullPlayerMeta{ - Player: domain.PlayerMeta{ - ID: 125, - Tribe: domain.NullTribeMeta{ - Tribe: domain.TribeMeta{ - ID: 150, - }, - Valid: true, + NewOwner: domain.PlayerMeta{ + ID: 125, + Tribe: domain.NullTribeMeta{ + Tribe: domain.TribeMeta{ + ID: 150, }, + Valid: true, }, - Valid: true, }, Village: domain.VillageMeta{ Player: domain.NullPlayerMeta{ @@ -63,17 +60,14 @@ func TestGroupWithMonitors_CanSendEnnoblementNotificationTypeGain(t *testing.T) }, }, }.CanSendEnnoblementNotificationTypeGain(domain.Ennoblement{ - NewOwner: domain.NullPlayerMeta{ - Player: domain.PlayerMeta{ - ID: 125, - Tribe: domain.NullTribeMeta{ - Tribe: domain.TribeMeta{ - ID: 150, - }, - Valid: true, + NewOwner: domain.PlayerMeta{ + ID: 125, + Tribe: domain.NullTribeMeta{ + Tribe: domain.TribeMeta{ + ID: 150, }, + Valid: true, }, - Valid: true, }, Village: domain.VillageMeta{ Player: domain.NullPlayerMeta{ @@ -105,17 +99,14 @@ func TestGroupWithMonitors_CanSendEnnoblementNotificationTypeGain(t *testing.T) }, }, }.CanSendEnnoblementNotificationTypeGain(domain.Ennoblement{ - NewOwner: domain.NullPlayerMeta{ - Player: domain.PlayerMeta{ - ID: 125, - Tribe: domain.NullTribeMeta{ - Tribe: domain.TribeMeta{ - ID: 150, - }, - Valid: true, + NewOwner: domain.PlayerMeta{ + ID: 125, + Tribe: domain.NullTribeMeta{ + Tribe: domain.TribeMeta{ + ID: 150, }, + Valid: true, }, - Valid: true, }, })) @@ -132,17 +123,14 @@ func TestGroupWithMonitors_CanSendEnnoblementNotificationTypeGain(t *testing.T) }, }, }.CanSendEnnoblementNotificationTypeGain(domain.Ennoblement{ - NewOwner: domain.NullPlayerMeta{ - Player: domain.PlayerMeta{ - ID: 125, - Tribe: domain.NullTribeMeta{ - Tribe: domain.TribeMeta{ - ID: 150, - }, - Valid: true, + NewOwner: domain.PlayerMeta{ + ID: 125, + Tribe: domain.NullTribeMeta{ + Tribe: domain.TribeMeta{ + ID: 150, }, + Valid: true, }, - Valid: true, }, })) @@ -160,17 +148,14 @@ func TestGroupWithMonitors_CanSendEnnoblementNotificationTypeGain(t *testing.T) }, }, }.CanSendEnnoblementNotificationTypeGain(domain.Ennoblement{ - NewOwner: domain.NullPlayerMeta{ - Player: domain.PlayerMeta{ - ID: 125, - Tribe: domain.NullTribeMeta{ - Tribe: domain.TribeMeta{ - ID: 150, - }, - Valid: true, + NewOwner: domain.PlayerMeta{ + ID: 125, + Tribe: domain.NullTribeMeta{ + Tribe: domain.TribeMeta{ + ID: 150, }, + Valid: true, }, - Valid: true, }, Village: domain.VillageMeta{ Player: domain.NullPlayerMeta{ @@ -201,17 +186,14 @@ func TestGroupWithMonitors_CanSendEnnoblementNotificationTypeGain(t *testing.T) }, }, }.CanSendEnnoblementNotificationTypeGain(domain.Ennoblement{ - NewOwner: domain.NullPlayerMeta{ - Player: domain.PlayerMeta{ - ID: 125, - Tribe: domain.NullTribeMeta{ - Tribe: domain.TribeMeta{ - ID: 150, - }, - Valid: true, + NewOwner: domain.PlayerMeta{ + ID: 125, + Tribe: domain.NullTribeMeta{ + Tribe: domain.TribeMeta{ + ID: 150, }, + Valid: true, }, - Valid: true, }, Village: domain.VillageMeta{ Player: domain.NullPlayerMeta{ @@ -246,17 +228,14 @@ func TestGroupWithMonitors_CanSendEnnoblementNotificationTypeLoss(t *testing.T) }, }, }.CanSendEnnoblementNotificationTypeLoss(domain.Ennoblement{ - NewOwner: domain.NullPlayerMeta{ - Player: domain.PlayerMeta{ - ID: 125, - Tribe: domain.NullTribeMeta{ - Tribe: domain.TribeMeta{ - ID: 150, - }, - Valid: true, + NewOwner: domain.PlayerMeta{ + ID: 125, + Tribe: domain.NullTribeMeta{ + Tribe: domain.TribeMeta{ + ID: 150, }, + Valid: true, }, - Valid: true, }, Village: domain.VillageMeta{ Player: domain.NullPlayerMeta{ @@ -284,17 +263,14 @@ func TestGroupWithMonitors_CanSendEnnoblementNotificationTypeLoss(t *testing.T) }, }, }.CanSendEnnoblementNotificationTypeLoss(domain.Ennoblement{ - NewOwner: domain.NullPlayerMeta{ - Player: domain.PlayerMeta{ - ID: 125, - Tribe: domain.NullTribeMeta{ - Tribe: domain.TribeMeta{ - ID: 150, - }, - Valid: true, + NewOwner: domain.PlayerMeta{ + ID: 125, + Tribe: domain.NullTribeMeta{ + Tribe: domain.TribeMeta{ + ID: 150, }, + Valid: true, }, - Valid: true, }, Village: domain.VillageMeta{ Player: domain.NullPlayerMeta{ @@ -325,17 +301,14 @@ func TestGroupWithMonitors_CanSendEnnoblementNotificationTypeLoss(t *testing.T) }, }, }.CanSendEnnoblementNotificationTypeLoss(domain.Ennoblement{ - NewOwner: domain.NullPlayerMeta{ - Player: domain.PlayerMeta{ - ID: 125, - Tribe: domain.NullTribeMeta{ - Tribe: domain.TribeMeta{ - ID: 150, - }, - Valid: true, + NewOwner: domain.PlayerMeta{ + ID: 125, + Tribe: domain.NullTribeMeta{ + Tribe: domain.TribeMeta{ + ID: 150, }, + Valid: true, }, - Valid: true, }, Village: domain.VillageMeta{ Player: domain.NullPlayerMeta{ @@ -367,17 +340,14 @@ func TestGroupWithMonitors_IsGain(t *testing.T) { }, }, }.IsGain(domain.Ennoblement{ - NewOwner: domain.NullPlayerMeta{ - Player: domain.PlayerMeta{ - ID: 125, - Tribe: domain.NullTribeMeta{ - Tribe: domain.TribeMeta{ - ID: 150, - }, - Valid: true, + NewOwner: domain.PlayerMeta{ + ID: 125, + Tribe: domain.NullTribeMeta{ + Tribe: domain.TribeMeta{ + ID: 150, }, + Valid: true, }, - Valid: true, }, })) @@ -391,17 +361,14 @@ func TestGroupWithMonitors_IsGain(t *testing.T) { }, }, }.IsGain(domain.Ennoblement{ - NewOwner: domain.NullPlayerMeta{ - Player: domain.PlayerMeta{ - ID: 125, - Tribe: domain.NullTribeMeta{ - Tribe: domain.TribeMeta{ - ID: 150, - }, - Valid: true, + NewOwner: domain.PlayerMeta{ + ID: 125, + Tribe: domain.NullTribeMeta{ + Tribe: domain.TribeMeta{ + ID: 150, }, + Valid: true, }, - Valid: true, }, Village: domain.VillageMeta{ Player: domain.NullPlayerMeta{ @@ -429,16 +396,13 @@ func TestGroupWithMonitors_IsGain(t *testing.T) { }, }, }.IsGain(domain.Ennoblement{ - NewOwner: domain.NullPlayerMeta{ - Player: domain.PlayerMeta{ - Tribe: domain.NullTribeMeta{ - Tribe: domain.TribeMeta{ - ID: 150, - }, - Valid: true, + NewOwner: domain.PlayerMeta{ + Tribe: domain.NullTribeMeta{ + Tribe: domain.TribeMeta{ + ID: 150, }, + Valid: true, }, - Valid: true, }, })) @@ -493,17 +457,14 @@ func TestGroupWithMonitors_IsLoss(t *testing.T) { }, }, }.IsLoss(domain.Ennoblement{ - NewOwner: domain.NullPlayerMeta{ - Player: domain.PlayerMeta{ - ID: 125, - Tribe: domain.NullTribeMeta{ - Tribe: domain.TribeMeta{ - ID: 150, - }, - Valid: true, + NewOwner: domain.PlayerMeta{ + ID: 125, + Tribe: domain.NullTribeMeta{ + Tribe: domain.TribeMeta{ + ID: 150, }, + Valid: true, }, - Valid: true, }, Village: domain.VillageMeta{ Player: domain.NullPlayerMeta{ @@ -572,17 +533,14 @@ func TestGroupWithMonitors_IsInternal(t *testing.T) { }, }, }.IsInternal(domain.Ennoblement{ - NewOwner: domain.NullPlayerMeta{ - Player: domain.PlayerMeta{ - ID: 125, - Tribe: domain.NullTribeMeta{ - Tribe: domain.TribeMeta{ - ID: 150, - }, - Valid: true, + NewOwner: domain.PlayerMeta{ + ID: 125, + Tribe: domain.NullTribeMeta{ + Tribe: domain.TribeMeta{ + ID: 150, }, + Valid: true, }, - Valid: true, }, Village: domain.VillageMeta{ Player: domain.NullPlayerMeta{ @@ -610,17 +568,14 @@ func TestGroupWithMonitors_IsInternal(t *testing.T) { }, }, }.IsInternal(domain.Ennoblement{ - NewOwner: domain.NullPlayerMeta{ - Player: domain.PlayerMeta{ - ID: 125, - Tribe: domain.NullTribeMeta{ - Tribe: domain.TribeMeta{ - ID: 150, - }, - Valid: true, + NewOwner: domain.PlayerMeta{ + ID: 125, + Tribe: domain.NullTribeMeta{ + Tribe: domain.TribeMeta{ + ID: 150, }, + Valid: true, }, - Valid: true, }, Village: domain.VillageMeta{ Player: domain.NullPlayerMeta{ @@ -648,17 +603,14 @@ func TestGroupWithMonitors_IsInternal(t *testing.T) { }, }, }.IsInternal(domain.Ennoblement{ - NewOwner: domain.NullPlayerMeta{ - Player: domain.PlayerMeta{ - ID: 125, - Tribe: domain.NullTribeMeta{ - Tribe: domain.TribeMeta{ - ID: 150, - }, - Valid: true, + NewOwner: domain.PlayerMeta{ + ID: 125, + Tribe: domain.NullTribeMeta{ + Tribe: domain.TribeMeta{ + ID: 150, }, + Valid: true, }, - Valid: true, }, })) diff --git a/internal/domain/monitor.go b/internal/domain/monitor.go index ac5b778..8fce564 100644 --- a/internal/domain/monitor.go +++ b/internal/domain/monitor.go @@ -7,7 +7,7 @@ import ( type Monitor struct { ID string - TribeID int64 + TribeID int GroupID string CreatedAt time.Time } @@ -18,7 +18,7 @@ type MonitorWithTribe struct { } type MonitorAlreadyExistsError struct { - TribeID int64 + TribeID int GroupID string } diff --git a/internal/domain/player.go b/internal/domain/player.go index 4f7901d..eb8a588 100644 --- a/internal/domain/player.go +++ b/internal/domain/player.go @@ -1,7 +1,7 @@ package domain type PlayerMeta struct { - ID int64 + ID int Name string ProfileURL string Tribe NullTribeMeta diff --git a/internal/domain/tribe.go b/internal/domain/tribe.go index 6f24169..87442ad 100644 --- a/internal/domain/tribe.go +++ b/internal/domain/tribe.go @@ -6,7 +6,7 @@ import ( ) type Tribe struct { - ID int64 + ID int Tag string Name string ProfileURL string @@ -14,7 +14,7 @@ type Tribe struct { } type TribeMeta struct { - ID int64 + ID int Name string Tag string ProfileURL string @@ -52,7 +52,7 @@ func (e TribeTagNotFoundError) Params() map[string]any { type TribeIDNotFoundError struct { VersionCode string ServerKey string - ID int64 + ID int } var _ TranslatableError = TribeIDNotFoundError{} diff --git a/internal/domain/village.go b/internal/domain/village.go index 188e7af..a0ce6e2 100644 --- a/internal/domain/village.go +++ b/internal/domain/village.go @@ -10,12 +10,12 @@ import ( ) type Village struct { - ID int64 + ID int FullName string ProfileURL string - Points int64 - X int64 - Y int64 + Points int + X int + Y int Player NullPlayerMeta } @@ -24,7 +24,7 @@ func (v Village) Coords() string { } type VillageMeta struct { - ID int64 + ID int FullName string ProfileURL string Player NullPlayerMeta @@ -76,8 +76,8 @@ type TranslateVillageCoordsParams struct { versionCode string serverKey string coords []string - perPage int32 - maxPage int32 + perPage int + maxPage int sha256 string } @@ -88,7 +88,7 @@ const ( translateVillageCoordsMinPerPage = 1 ) -func NewTranslateVillageCoordsParams(versionCode, serverKey, coordsStr string, perPage int32) (TranslateVillageCoordsParams, error) { +func NewTranslateVillageCoordsParams(versionCode, serverKey, coordsStr string, perPage int) (TranslateVillageCoordsParams, error) { if versionCode == "" { return TranslateVillageCoordsParams{}, RequiredError{Field: "VersionCode"} } @@ -125,7 +125,7 @@ func NewTranslateVillageCoordsParams(versionCode, serverKey, coordsStr string, p serverKey: serverKey, coords: coords, perPage: perPage, - maxPage: int32(math.Ceil(float64(len(coords)) / float64(perPage))), + maxPage: int(math.Ceil(float64(len(coords)) / float64(perPage))), sha256: hex.EncodeToString(sum256[:]), }, nil } @@ -138,8 +138,8 @@ func UnmarshalTranslateVillageCoordsParamsFromDatabase( versionCode string, serverKey string, coords []string, - perPage int32, - maxPage int32, + perPage int, + maxPage int, sha256Hash string, ) (TranslateVillageCoordsParams, error) { if versionCode == "" { @@ -190,11 +190,11 @@ func (t TranslateVillageCoordsParams) Coords() []string { return t.coords } -func (t TranslateVillageCoordsParams) PerPage() int32 { +func (t TranslateVillageCoordsParams) PerPage() int { return t.perPage } -func (t TranslateVillageCoordsParams) MaxPage() int32 { +func (t TranslateVillageCoordsParams) MaxPage() int { return t.maxPage } @@ -202,13 +202,13 @@ func (t TranslateVillageCoordsParams) SHA256() string { return t.sha256 } -func (t TranslateVillageCoordsParams) IsPageInRange(page int32) error { +func (t TranslateVillageCoordsParams) IsPageInRange(page int) error { if page < 1 { return GreaterEqualThanError{Field: "page", Threshold: 1} } if page > t.maxPage { - return LessEqualThanError{Field: "page", Threshold: int(t.maxPage)} + return LessEqualThanError{Field: "page", Threshold: t.maxPage} } return nil @@ -219,8 +219,8 @@ type TranslateVillageCoordsResult struct { NotFound []string HasNext bool HasPrev bool - Page int32 - MaxPage int32 + Page int + MaxPage int ParamsSHA256 string } diff --git a/internal/domain/village_test.go b/internal/domain/village_test.go index 391a0ff..5408472 100644 --- a/internal/domain/village_test.go +++ b/internal/domain/village_test.go @@ -22,8 +22,8 @@ func TestNewTranslateVillageCoordsParams(t *testing.T) { serverKey string coordsStr string coords []string - perPage int32 - maxPage int32 + perPage int + maxPage int err error }{ { diff --git a/internal/service/group.go b/internal/service/group.go index aa267e1..bb56d43 100644 --- a/internal/service/group.go +++ b/internal/service/group.go @@ -15,7 +15,7 @@ import ( type GroupRepository interface { Create(ctx context.Context, params domain.CreateGroupParams) (domain.GroupWithMonitors, error) Update(ctx context.Context, id string, params domain.UpdateGroupParams) (domain.GroupWithMonitors, error) - AddMonitor(ctx context.Context, id string, tribeID int64) (domain.GroupWithMonitors, error) + AddMonitor(ctx context.Context, id string, tribeID int) (domain.GroupWithMonitors, error) DeleteMonitors(ctx context.Context, id string, monitorID ...string) (domain.GroupWithMonitors, error) List(ctx context.Context, params domain.ListGroupsParams) ([]domain.GroupWithMonitors, error) Get(ctx context.Context, id string) (domain.GroupWithMonitors, error) @@ -110,7 +110,7 @@ func (g *Group) RemoveTribe(ctx context.Context, id, serverID, tribeTag string) return domain.GroupWithMonitors{}, err } - tribes, err := g.twhelpSvc.ListTribesByTag(ctx, groupBeforeUpdate.VersionCode, groupBeforeUpdate.ServerKey, []string{tribeTag}, 0, listTribesLimit) + tribes, err := g.twhelpSvc.ListTribesByTag(ctx, groupBeforeUpdate.VersionCode, groupBeforeUpdate.ServerKey, []string{tribeTag}, listTribesLimit) if err != nil { return domain.GroupWithMonitors{}, fmt.Errorf("TWHelpClient.ListTribes: %w", err) } @@ -372,7 +372,7 @@ func (g *Group) listEnnoblements(ctx context.Context, groups []domain.GroupWithM go func(group domain.GroupWithMonitors, since time.Time) { defer wg.Done() - ennoblements, err := g.twhelpSvc.ListEnnoblementsSince(ctx, group.VersionCode, group.ServerKey, since, 0, listEnnoblementsLimit) + ennoblements, err := g.twhelpSvc.ListEnnoblementsSince(ctx, group.VersionCode, group.ServerKey, since, listEnnoblementsLimit) if err != nil { g.logger.Warn( "couldn't list ennoblements", @@ -461,7 +461,7 @@ func (g *Group) deleteAllWithClosedTWServers(ctx context.Context) error { } for _, v := range versions { - servers, err := g.twhelpSvc.ListClosedServers(ctx, v.Code, 0, listServersLimit) + servers, err := g.twhelpSvc.ListClosedServers(ctx, v.Code, listServersLimit) if err != nil { g.logger.Warn("couldn't list closed servers", zap.Error(err), zap.String("versionCode", v.Code)) continue diff --git a/internal/service/group_test.go b/internal/service/group_test.go index ad877f3..8290c7e 100644 --- a/internal/service/group_test.go +++ b/internal/service/group_test.go @@ -111,7 +111,7 @@ func TestGroup_AddTribe(t *testing.T) { }, } repo.GetReturns(group, nil) - repo.AddMonitorCalls(func(ctx context.Context, id string, tribeID int64) (domain.GroupWithMonitors, error) { + repo.AddMonitorCalls(func(ctx context.Context, id string, tribeID int) (domain.GroupWithMonitors, error) { group.Monitors = append(group.Monitors, domain.Monitor{ ID: uuid.NewString(), GroupID: id, @@ -907,7 +907,7 @@ func TestGroup_GetWithTribes(t *testing.T) { repo.GetReturns(group, nil) twhelpSvc := &mock.FakeTWHelpService{} - twhelpSvc.GetTribeByIDCalls(func(ctx context.Context, versionCode string, serverKey string, id int64) (domain.Tribe, error) { + twhelpSvc.GetTribeByIDCalls(func(ctx context.Context, versionCode string, serverKey string, id int) (domain.Tribe, error) { return domain.Tribe{ ID: id, Tag: fmt.Sprintf("*%d*", id), @@ -1007,7 +1007,7 @@ func TestGroup_GetWithTribes(t *testing.T) { twhelpSvc := &mock.FakeTWHelpService{} expectedErr := errors.New("err") - twhelpSvc.GetTribeByIDCalls(func(ctx context.Context, versionCode string, serverKey string, id int64) (domain.Tribe, error) { + twhelpSvc.GetTribeByIDCalls(func(ctx context.Context, versionCode string, serverKey string, id int) (domain.Tribe, error) { if id == 115 { return domain.Tribe{}, expectedErr } @@ -1367,132 +1367,93 @@ func TestGroup_Execute(t *testing.T) { ennoblements := map[string][]domain.Ennoblement{ "pl:pl181": { { - ID: 1, - Village: villages["pl:pl181"][0], - NewOwner: domain.NullPlayerMeta{ - Player: players["pl:pl181"][0], - Valid: true, - }, + ID: 1, + Village: villages["pl:pl181"][0], + NewOwner: players["pl:pl181"][0], CreatedAt: time.Now().Add(-5 * time.Minute), }, { - ID: 2, // self conquer, should be skipped - Village: villages["pl:pl181"][1], - NewOwner: domain.NullPlayerMeta{ - Player: players["pl:pl181"][0], - Valid: true, - }, + ID: 2, // self conquer, should be skipped + Village: villages["pl:pl181"][1], + NewOwner: players["pl:pl181"][0], CreatedAt: time.Now().Add(-4 * time.Minute), }, { - ID: 3, // internal, should be skipped (internals disabled) - Village: villages["pl:pl181"][2], - NewOwner: domain.NullPlayerMeta{ - Player: players["pl:pl181"][0], - Valid: true, - }, + ID: 3, // internal, should be skipped (internals disabled) + Village: villages["pl:pl181"][2], + NewOwner: players["pl:pl181"][0], CreatedAt: time.Now().Add(-3 * time.Minute), }, { - ID: 4, // barbarian, shouldn't be skipped (barbarians enabled) - Village: villages["pl:pl181"][3], - NewOwner: domain.NullPlayerMeta{ - Player: players["pl:pl181"][0], - Valid: true, - }, + ID: 4, // barbarian, shouldn't be skipped (barbarians enabled) + Village: villages["pl:pl181"][3], + NewOwner: players["pl:pl181"][0], CreatedAt: time.Now().Add(-5 * time.Minute), }, { - ID: 5, // disabled notifications about gains, should be skipped - Village: villages["pl:pl181"][4], - NewOwner: domain.NullPlayerMeta{ - Player: players["pl:pl181"][1], - Valid: true, - }, + ID: 5, // disabled notifications about gains, should be skipped + Village: villages["pl:pl181"][4], + NewOwner: players["pl:pl181"][1], CreatedAt: time.Now().Add(-5 * time.Minute), }, { - ID: 6, // disabled notifications about losses, should be skipped - Village: villages["pl:pl181"][4], - NewOwner: domain.NullPlayerMeta{ - Player: players["pl:pl181"][3], - Valid: true, - }, + ID: 6, // disabled notifications about losses, should be skipped + Village: villages["pl:pl181"][4], + NewOwner: players["pl:pl181"][3], CreatedAt: time.Now().Add(-5 * time.Minute), }, }, "en:en130": { { - ID: 100, // no monitor for these tribes, should be skipped - Village: villages["en:en130"][0], - NewOwner: domain.NullPlayerMeta{ - Player: players["en:en130"][0], - Valid: true, - }, + ID: 100, // no monitor for these tribes, should be skipped + Village: villages["en:en130"][0], + NewOwner: players["en:en130"][0], CreatedAt: time.Now().Add(-5 * time.Minute), }, { - ID: 101, // no monitor for these tribes, should be skipped - Village: villages["en:en130"][1], - NewOwner: domain.NullPlayerMeta{ - Player: players["en:en130"][2], - Valid: true, - }, + ID: 101, // no monitor for these tribes, should be skipped + Village: villages["en:en130"][1], + NewOwner: players["en:en130"][2], CreatedAt: time.Now().Add(-5 * time.Minute), }, { - ID: 102, - Village: villages["en:en130"][2], - NewOwner: domain.NullPlayerMeta{ - Player: players["en:en130"][1], - Valid: true, - }, + ID: 102, + Village: villages["en:en130"][2], + NewOwner: players["en:en130"][1], CreatedAt: time.Now().Add(-5 * time.Minute), }, }, "pl:pl180": { { - ID: 200, // api error, should be skipped - Village: villages["pl:pl180"][0], - NewOwner: domain.NullPlayerMeta{ - Player: players["pl:pl180"][0], - Valid: true, - }, + ID: 200, // api error, should be skipped + Village: villages["pl:pl180"][0], + NewOwner: players["pl:pl180"][0], CreatedAt: time.Now().Add(-5 * time.Minute), }, }, "de:de200": { { - ID: 300, // barbarian, should be skipped (barbarians disabled) - Village: villages["de:de200"][0], - NewOwner: domain.NullPlayerMeta{ - Player: players["de:de200"][0], - Valid: true, - }, + ID: 300, // barbarian, should be skipped (barbarians disabled) + Village: villages["de:de200"][0], + NewOwner: players["de:de200"][0], CreatedAt: time.Now().Add(-5 * time.Minute), }, { - ID: 301, // internal, shouldn't be skipped (internals enabled) - Village: villages["de:de200"][1], - NewOwner: domain.NullPlayerMeta{ - Player: players["de:de200"][0], - Valid: true, - }, + ID: 301, // internal, shouldn't be skipped (internals enabled) + Village: villages["de:de200"][1], + NewOwner: players["de:de200"][0], CreatedAt: time.Now().Add(-5 * time.Minute), }, { - ID: 302, // internal, shouldn't be skipped (internals enabled) - Village: villages["de:de200"][2], - NewOwner: domain.NullPlayerMeta{ - Player: players["de:de200"][0], - Valid: true, - }, + ID: 302, // internal, shouldn't be skipped (internals enabled) + Village: villages["de:de200"][2], + NewOwner: players["de:de200"][0], CreatedAt: time.Now().Add(-5 * time.Minute), }, }, } twhelpSvc.ListEnnoblementsSinceCalls( - func(ctx context.Context, version string, server string, _ time.Time, _ int32, _ int32) ([]domain.Ennoblement, error) { + func(ctx context.Context, version string, server string, _ time.Time, _ int) ([]domain.Ennoblement, error) { if version == "pl" && server == "pl180" { return nil, errors.New("random error") } @@ -1672,7 +1633,7 @@ func TestGroup_CleanUp(t *testing.T) { }, }, } - twhelpSvc.ListClosedServersCalls(func(ctx context.Context, version string, _, _ int32) ([]domain.TWServer, error) { + twhelpSvc.ListClosedServersCalls(func(ctx context.Context, version string, _ int) ([]domain.TWServer, error) { return serversByVersion[version], nil }) diff --git a/internal/service/service.go b/internal/service/service.go index b2fbe69..875a6f1 100644 --- a/internal/service/service.go +++ b/internal/service/service.go @@ -12,13 +12,13 @@ import ( //counterfeiter:generate -o internal/mock/twhelp_service.gen.go . TWHelpService type TWHelpService interface { ListVersions(ctx context.Context) ([]domain.TWVersion, error) - ListOpenServers(ctx context.Context, version string, offset, limit int32) ([]domain.TWServer, error) - ListClosedServers(ctx context.Context, version string, offset, limit int32) ([]domain.TWServer, error) + ListOpenServers(ctx context.Context, version string, limit int) ([]domain.TWServer, error) + ListClosedServers(ctx context.Context, version string, limit int) ([]domain.TWServer, error) GetOpenServer(ctx context.Context, versionCode, serverKey string) (domain.TWServer, error) GetServer(ctx context.Context, versionCode, serverKey string) (domain.TWServer, error) - GetTribeByID(ctx context.Context, versionCode, serverKey string, id int64) (domain.Tribe, error) + GetTribeByID(ctx context.Context, versionCode, serverKey string, id int) (domain.Tribe, error) GetExistingTribeByTag(ctx context.Context, versionCode, serverKey, tribeTag string) (domain.Tribe, error) - ListTribesByTag(ctx context.Context, versionCode, serverKey string, tribeTags []string, offset, limit int32) ([]domain.Tribe, error) - ListVillagesByCoords(ctx context.Context, versionCode, serverKey string, coords []string, offset, limit int32) ([]domain.Village, error) - ListEnnoblementsSince(ctx context.Context, versionCode, serverKey string, since time.Time, offset, limit int32) ([]domain.Ennoblement, error) + ListTribesByTag(ctx context.Context, versionCode, serverKey string, tribeTags []string, limit int) ([]domain.Tribe, error) + ListVillagesByCoords(ctx context.Context, versionCode, serverKey string, coords []string, limit int) ([]domain.Village, error) + ListEnnoblementsSince(ctx context.Context, versionCode, serverKey string, since time.Time, limit int) ([]domain.Ennoblement, error) } diff --git a/internal/service/village.go b/internal/service/village.go index 49caa98..32de0a9 100644 --- a/internal/service/village.go +++ b/internal/service/village.go @@ -24,7 +24,7 @@ func NewVillage(repo VillageRepository, twhelpSvc TWHelpService) *Village { func (v *Village) TranslateCoords( ctx context.Context, params domain.TranslateVillageCoordsParams, - page int32, + page int, ) (domain.TranslateVillageCoordsResult, error) { if err := params.IsPageInRange(page); err != nil { return domain.TranslateVillageCoordsResult{}, err @@ -40,13 +40,13 @@ func (v *Village) TranslateCoords( low := params.PerPage() * (page - 1) high := params.PerPage() * page - if coordsLen := int32(len(params.Coords())); coordsLen < high { + if coordsLen := len(params.Coords()); coordsLen < high { high = coordsLen } coords := params.Coords()[low:high] - villages, err := v.twhelpSvc.ListVillagesByCoords(ctx, params.VersionCode(), params.ServerKey(), coords, 0, high-low) + villages, err := v.twhelpSvc.ListVillagesByCoords(ctx, params.VersionCode(), params.ServerKey(), coords, high-low) if err != nil { return domain.TranslateVillageCoordsResult{}, err } @@ -78,7 +78,7 @@ func (v *Village) TranslateCoords( }, nil } -func (v *Village) TranslateCoordsFromHash(ctx context.Context, paramsSHA256Hash string, page int32) (domain.TranslateVillageCoordsResult, error) { +func (v *Village) TranslateCoordsFromHash(ctx context.Context, paramsSHA256Hash string, page int) (domain.TranslateVillageCoordsResult, error) { params, err := v.repo.GetTranslateCoordsParams(ctx, paramsSHA256Hash) if err != nil { return domain.TranslateVillageCoordsResult{}, err diff --git a/internal/service/village_test.go b/internal/service/village_test.go index dba8442..0c960fb 100644 --- a/internal/service/village_test.go +++ b/internal/service/village_test.go @@ -57,7 +57,7 @@ func TestVillage_TranslateCoords(t *testing.T) { require.NoError(t, err) tests := []struct { - page int32 + page int hasNext bool hasPrev bool villages []domain.Village @@ -105,8 +105,7 @@ func TestVillage_TranslateCoords(t *testing.T) { versionCode string, serverKey string, coords []string, - offset int32, - perPage int32, + perPage int, ) ([]domain.Village, error) { if versionCode != params.VersionCode() || serverKey != params.ServerKey() { return nil, nil diff --git a/internal/twhelp/.gitignore b/internal/twhelp/.gitignore new file mode 100644 index 0000000..bc7b52c --- /dev/null +++ b/internal/twhelp/.gitignore @@ -0,0 +1 @@ +*.gen.go diff --git a/internal/twhelp/client.go b/internal/twhelp/client.go deleted file mode 100644 index 198f9c6..0000000 --- a/internal/twhelp/client.go +++ /dev/null @@ -1,291 +0,0 @@ -package twhelp - -import ( - "context" - "encoding/json" - "fmt" - "io" - "net/http" - "net/url" - "strconv" - "time" - - "golang.org/x/time/rate" -) - -const ( - defaultUserAgent = "TWHelpDCBot/development" - defaultTimeout = 10 * time.Second - - endpointListVersions = "/api/v1/versions" - endpointListServers = "/api/v1/versions/%s/servers" - endpointGetServer = "/api/v1/versions/%s/servers/%s" - endpointListTribes = "/api/v1/versions/%s/servers/%s/tribes" - endpointGetTribeByID = "/api/v1/versions/%s/servers/%s/tribes/%d" - endpointListEnnoblements = "/api/v1/versions/%s/servers/%s/ennoblements" - endpointListVillages = "/api/v1/versions/%s/servers/%s/villages" -) - -type Client struct { - userAgent string - client *http.Client - baseURL *url.URL - rateLimiter *rate.Limiter -} - -type ClientOption func(c *Client) - -func WithHTTPClient(hc *http.Client) ClientOption { - return func(c *Client) { - c.client = hc - } -} - -func WithUserAgent(ua string) ClientOption { - return func(c *Client) { - c.userAgent = ua - } -} - -func WithRateLimiter(rl *rate.Limiter) ClientOption { - return func(c *Client) { - c.rateLimiter = rl - } -} - -func NewClient(baseURL *url.URL, opts ...ClientOption) *Client { - c := &Client{ - baseURL: baseURL, - userAgent: defaultUserAgent, - client: &http.Client{ - Timeout: defaultTimeout, - }, - } - for _, opt := range opts { - opt(c) - } - return c -} - -func (c *Client) ListVersions(ctx context.Context) ([]Version, error) { - var resp listVersionsResp - if err := c.getJSON(ctx, endpointListVersions, &resp); err != nil { - return nil, err - } - return resp.Data, nil -} - -type ListServersQueryParams struct { - Limit int32 - Offset int32 - Open NullBool -} - -func (c *Client) ListServers( - ctx context.Context, - version string, - params ListServersQueryParams, -) ([]Server, error) { - q := url.Values{} - - if params.Limit > 0 { - q.Set("limit", strconv.FormatInt(int64(params.Limit), 10)) - } - - if params.Offset > 0 { - q.Set("offset", strconv.FormatInt(int64(params.Offset), 10)) - } - - if params.Open.Valid { - q.Set("open", strconv.FormatBool(params.Open.Bool)) - } - - var resp listServersResp - if err := c.getJSON(ctx, fmt.Sprintf(endpointListServers, version)+"?"+q.Encode(), &resp); err != nil { - return nil, err - } - - return resp.Data, nil -} - -func (c *Client) GetServer(ctx context.Context, version, server string) (Server, error) { - var resp getServerResp - if err := c.getJSON(ctx, fmt.Sprintf(endpointGetServer, version, server), &resp); err != nil { - return Server{}, err - } - return resp.Data, nil -} - -type ListTribesQueryParams struct { - Limit int32 - Offset int32 - Tags []string - Deleted NullBool -} - -func (c *Client) ListTribes( - ctx context.Context, - version, server string, - params ListTribesQueryParams, -) ([]Tribe, error) { - q := url.Values{} - - if params.Limit > 0 { - q.Set("limit", strconv.FormatInt(int64(params.Limit), 10)) - } - - if params.Offset > 0 { - q.Set("offset", strconv.FormatInt(int64(params.Offset), 10)) - } - - if params.Deleted.Valid { - q.Set("deleted", strconv.FormatBool(params.Deleted.Bool)) - } - - if params.Tags != nil { - for _, t := range params.Tags { - q.Add("tag", t) - } - } - - var resp listTribesResp - if err := c.getJSON(ctx, fmt.Sprintf(endpointListTribes, version, server)+"?"+q.Encode(), &resp); err != nil { - return nil, err - } - - return resp.Data, nil -} - -func (c *Client) GetTribeByID(ctx context.Context, version, server string, id int64) (Tribe, error) { - var resp getTribeResp - if err := c.getJSON(ctx, fmt.Sprintf(endpointGetTribeByID, version, server, id), &resp); err != nil { - return Tribe{}, err - } - return resp.Data, nil -} - -type EnnoblementSort string - -const ( - EnnoblementSortCreatedAtASC EnnoblementSort = "createdAt:ASC" -) - -func (s EnnoblementSort) String() string { - return string(s) -} - -type ListEnnoblementsQueryParams struct { - Limit int32 - Offset int32 - Since time.Time - Sort []EnnoblementSort -} - -func (c *Client) ListEnnoblements( - ctx context.Context, - version, server string, - params ListEnnoblementsQueryParams, -) ([]Ennoblement, error) { - q := url.Values{} - - if params.Limit > 0 { - q.Set("limit", strconv.FormatInt(int64(params.Limit), 10)) - } - - if params.Offset > 0 { - q.Set("offset", strconv.FormatInt(int64(params.Offset), 10)) - } - - if !params.Since.IsZero() { - q.Set("since", params.Since.Format(time.RFC3339)) - } - - for _, s := range params.Sort { - q.Add("sort", s.String()) - } - - var resp listEnnoblementsResp - if err := c.getJSON(ctx, fmt.Sprintf(endpointListEnnoblements, version, server)+"?"+q.Encode(), &resp); err != nil { - return nil, err - } - - return resp.Data, nil -} - -type ListVillagesQueryParams struct { - Limit int32 - Offset int32 - Coords []string -} - -func (c *Client) ListVillages( - ctx context.Context, - version, server string, - params ListVillagesQueryParams, -) ([]Village, error) { - q := url.Values{} - - if params.Limit > 0 { - q.Set("limit", strconv.FormatInt(int64(params.Limit), 10)) - } - - if params.Offset > 0 { - q.Set("offset", strconv.FormatInt(int64(params.Offset), 10)) - } - - for _, co := range params.Coords { - q.Add("coords", co) - } - - var resp listVillagesResp - if err := c.getJSON(ctx, fmt.Sprintf(endpointListVillages, version, server)+"?"+q.Encode(), &resp); err != nil { - return nil, err - } - - return resp.Data, nil -} - -func (c *Client) getJSON(ctx context.Context, urlStr string, v any) error { - u, err := c.baseURL.Parse(urlStr) - if err != nil { - return fmt.Errorf("c.baseURL.Parse: %w", err) - } - - req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil) - if err != nil { - return fmt.Errorf("http.NewRequestWithContext: %w", err) - } - - // headers - req.Header.Set("User-Agent", c.userAgent) - - // rate limiter - if c.rateLimiter != nil { - if err = c.rateLimiter.Wait(ctx); err != nil { - return err - } - } - - resp, err := c.client.Do(req) - if err != nil { - return fmt.Errorf("client.Do: %w", err) - } - defer func() { - _ = resp.Body.Close() - }() - - if resp.StatusCode != http.StatusOK { - var errResp errorResp - if err = json.NewDecoder(resp.Body).Decode(&errResp); err != nil { - _, _ = io.Copy(io.Discard, resp.Body) - return fmt.Errorf("got non-ok HTTP status: %d", resp.StatusCode) - } - return errResp.Error - } - - if err = json.NewDecoder(resp.Body).Decode(v); err != nil { - return fmt.Errorf("couldn't decode resp body: %w", err) - } - - return nil -} diff --git a/internal/twhelp/config.yml b/internal/twhelp/config.yml new file mode 100644 index 0000000..2145fdd --- /dev/null +++ b/internal/twhelp/config.yml @@ -0,0 +1,5 @@ +package: twhelp +generate: + models: true + client: true +output: twhelp.gen.go diff --git a/internal/twhelp/openapi3.json b/internal/twhelp/openapi3.json new file mode 100644 index 0000000..f65a5f7 --- /dev/null +++ b/internal/twhelp/openapi3.json @@ -0,0 +1 @@ +{"components":{"parameters":{"BeforeQueryParam":{"description":"only items created before the provided time are returned, this is a timestamp in RFC 3339 format","in":"query","name":"before","schema":{"format":"date-time","type":"string"}},"CursorQueryParam":{"in":"query","name":"cursor","schema":{"$ref":"#/components/schemas/CursorString"}},"EnnoblementSortQueryParam":{"description":"Order matters!","in":"query","name":"sort","schema":{"default":["createdAt:ASC"],"items":{"enum":["createdAt:ASC","createdAt:DESC"],"maxItems":1,"type":"string"},"type":"array"}},"LimitQueryParam":{"in":"query","name":"limit","schema":{"default":500,"maximum":500,"minimum":1,"type":"integer"}},"PlayerDeletedQueryParam":{"description":"true=only deleted players, false=only existing players, by default both existing and deleted players are returned","in":"query","name":"deleted","schema":{"type":"boolean"}},"PlayerIdPathParam":{"in":"path","name":"playerId","required":true,"schema":{"$ref":"#/components/schemas/IntId"}},"PlayerIdQueryParam":{"in":"query","name":"id","schema":{"items":{"$ref":"#/components/schemas/IntId"},"type":"array"}},"PlayerNameQueryParam":{"in":"query","name":"name","schema":{"items":{"maxLength":150,"minLength":1,"type":"string"},"maxItems":100,"type":"array"}},"PlayerSnapshotSortQueryParam":{"description":"Order matters!","in":"query","name":"sort","schema":{"default":["date:ASC"],"items":{"enum":["date:ASC","date:DESC"],"maxItems":1,"type":"string"},"type":"array"}},"PlayerSortQueryParam":{"description":"Order matters!","in":"query","name":"sort","schema":{"items":{"enum":["odScoreAtt:ASC","odScoreAtt:DESC","odScoreDef:ASC","odScoreDef:DESC","odScoreSup:ASC","odScoreSup:DESC","odScoreTotal:ASC","odScoreTotal:DESC","points:ASC","points:DESC","deletedAt:ASC","deletedAt:DESC"],"maxItems":2,"type":"string"},"type":"array"}},"ServerKeyPathParam":{"in":"path","name":"serverKey","required":true,"schema":{"$ref":"#/components/schemas/ServerKey"}},"ServerOpenQueryParam":{"description":"true=only open servers, false=only closed servers, by default both open and closed servers are returned","in":"query","name":"open","schema":{"type":"boolean"}},"SinceQueryParam":{"description":"only items created since the provided time are returned, this is a timestamp in RFC 3339 format","in":"query","name":"since","schema":{"format":"date-time","type":"string"}},"TribeChangeSortQueryParam":{"description":"Order matters!","in":"query","name":"sort","schema":{"default":["createdAt:ASC"],"items":{"enum":["createdAt:ASC","createdAt:DESC"],"maxItems":1,"type":"string"},"type":"array"}},"TribeDeletedQueryParam":{"description":"true=only deleted tribes, false=only existing tribes, by default both existing and deleted tribes are returned","in":"query","name":"deleted","schema":{"type":"boolean"}},"TribeIdPathParam":{"in":"path","name":"tribeId","required":true,"schema":{"$ref":"#/components/schemas/IntId"}},"TribeSnapshotSortQueryParam":{"description":"Order matters!","in":"query","name":"sort","schema":{"default":["date:ASC"],"items":{"enum":["date:ASC","date:DESC"],"maxItems":1,"type":"string"},"type":"array"}},"TribeSortQueryParam":{"description":"Order matters!","in":"query","name":"sort","schema":{"items":{"enum":["odScoreAtt:ASC","odScoreAtt:DESC","odScoreDef:ASC","odScoreDef:DESC","odScoreTotal:ASC","odScoreTotal:DESC","points:ASC","points:DESC","dominance:ASC","dominance:DESC","deletedAt:ASC","deletedAt:DESC"],"maxItems":2,"type":"string"},"type":"array"}},"TribeTagQueryParam":{"in":"query","name":"tag","schema":{"items":{"type":"string"},"maxItems":100,"type":"array"}},"VersionCodePathParam":{"in":"path","name":"versionCode","required":true,"schema":{"$ref":"#/components/schemas/VersionCode"}},"VillageCoordsQueryParam":{"in":"query","name":"coords","schema":{"items":{"example":"500|500","type":"string"},"maxItems":200,"type":"array"}},"VillageIdPathParam":{"in":"path","name":"villageId","required":true,"schema":{"$ref":"#/components/schemas/IntId"}}},"responses":{"ErrorResponse":{"content":{"application/json":{"schema":{"additionalProperties":false,"properties":{"errors":{"items":{"$ref":"#/components/schemas/Error"},"type":"array"}},"required":["errors"],"type":"object"}}},"description":"Default error response."},"GetBuildingInfoResponse":{"content":{"application/json":{"schema":{"properties":{"data":{"$ref":"#/components/schemas/BuildingInfo"}},"required":["data"],"type":"object"}}},"description":""},"GetPlayerResponse":{"content":{"application/json":{"schema":{"properties":{"data":{"$ref":"#/components/schemas/Player"}},"required":["data"],"type":"object"}}},"description":""},"GetServerConfigResponse":{"content":{"application/json":{"schema":{"properties":{"data":{"$ref":"#/components/schemas/ServerConfig"}},"required":["data"],"type":"object"}}},"description":""},"GetServerResponse":{"content":{"application/json":{"schema":{"properties":{"data":{"$ref":"#/components/schemas/Server"}},"required":["data"],"type":"object"}}},"description":""},"GetTribeResponse":{"content":{"application/json":{"schema":{"properties":{"data":{"$ref":"#/components/schemas/Tribe"}},"required":["data"],"type":"object"}}},"description":""},"GetUnitInfoResponse":{"content":{"application/json":{"schema":{"properties":{"data":{"$ref":"#/components/schemas/UnitInfo"}},"required":["data"],"type":"object"}}},"description":""},"GetVersionResponse":{"content":{"application/json":{"schema":{"properties":{"data":{"$ref":"#/components/schemas/Version"}},"required":["data"],"type":"object"}}},"description":""},"GetVillageResponse":{"content":{"application/json":{"schema":{"properties":{"data":{"$ref":"#/components/schemas/Village"}},"required":["data"],"type":"object"}}},"description":""},"ListEnnoblementsResponse":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/PaginationResponse"},{"properties":{"data":{"items":{"$ref":"#/components/schemas/Ennoblement"},"type":"array"}},"required":["data"],"type":"object"}]}}},"description":""},"ListPlayerSnapshotsResponse":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/PaginationResponse"},{"properties":{"data":{"items":{"$ref":"#/components/schemas/PlayerSnapshot"},"type":"array"}},"required":["data"],"type":"object"}]}}},"description":""},"ListPlayersResponse":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/PaginationResponse"},{"properties":{"data":{"items":{"$ref":"#/components/schemas/Player"},"type":"array"}},"required":["data"],"type":"object"}]}}},"description":""},"ListPlayersWithServersResponse":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/PaginationResponse"},{"properties":{"data":{"items":{"$ref":"#/components/schemas/PlayerWithServer"},"type":"array"}},"required":["data"],"type":"object"}]}}},"description":""},"ListServersResponse":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/PaginationResponse"},{"properties":{"data":{"items":{"$ref":"#/components/schemas/Server"},"type":"array"}},"required":["data"],"type":"object"}]}}},"description":""},"ListTribeChangesResponse":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/PaginationResponse"},{"properties":{"data":{"items":{"$ref":"#/components/schemas/TribeChange"},"type":"array"}},"required":["data"],"type":"object"}]}}},"description":""},"ListTribeSnapshotsResponse":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/PaginationResponse"},{"properties":{"data":{"items":{"$ref":"#/components/schemas/TribeSnapshot"},"type":"array"}},"required":["data"],"type":"object"}]}}},"description":""},"ListTribesResponse":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/PaginationResponse"},{"properties":{"data":{"items":{"$ref":"#/components/schemas/Tribe"},"type":"array"}},"required":["data"],"type":"object"}]}}},"description":""},"ListVersionsResponse":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/PaginationResponse"},{"properties":{"data":{"items":{"$ref":"#/components/schemas/Version"},"type":"array"}},"required":["data"],"type":"object"}]}}},"description":""},"ListVillagesResponse":{"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/PaginationResponse"},{"properties":{"data":{"items":{"$ref":"#/components/schemas/Village"},"type":"array"}},"required":["data"],"type":"object"}]}}},"description":""}},"schemas":{"Building":{"properties":{"buildTime":{"format":"double","type":"number"},"buildTimeFactor":{"format":"double","type":"number"},"iron":{"type":"integer"},"ironFactor":{"format":"double","type":"number"},"maxLevel":{"type":"integer"},"minLevel":{"type":"integer"},"pop":{"type":"integer"},"popFactor":{"format":"double","type":"number"},"stone":{"type":"integer"},"stoneFactor":{"format":"double","type":"number"},"wood":{"type":"integer"},"woodFactor":{"format":"double","type":"number"}},"required":["buildTime","buildTimeFactor","iron","ironFactor","maxLevel","minLevel","pop","popFactor","stone","stoneFactor","wood","woodFactor"],"type":"object"},"BuildingInfo":{"properties":{"barracks":{"$ref":"#/components/schemas/Building"},"farm":{"$ref":"#/components/schemas/Building"},"garage":{"$ref":"#/components/schemas/Building"},"hide":{"$ref":"#/components/schemas/Building"},"iron":{"$ref":"#/components/schemas/Building"},"main":{"$ref":"#/components/schemas/Building"},"market":{"$ref":"#/components/schemas/Building"},"place":{"$ref":"#/components/schemas/Building"},"smith":{"$ref":"#/components/schemas/Building"},"snob":{"$ref":"#/components/schemas/Building"},"stable":{"$ref":"#/components/schemas/Building"},"statue":{"$ref":"#/components/schemas/Building"},"stone":{"$ref":"#/components/schemas/Building"},"storage":{"$ref":"#/components/schemas/Building"},"wall":{"$ref":"#/components/schemas/Building"},"watchtower":{"$ref":"#/components/schemas/Building"},"wood":{"$ref":"#/components/schemas/Building"}},"required":["barracks","farm","garage","hide","iron","main","market","place","smith","snob","stable","statue","stone","storage","wall","watchtower","wood"],"type":"object"},"Cursor":{"properties":{"next":{"allOf":[{"$ref":"#/components/schemas/CursorString"}],"description":"Cursor pointing to the next page.","x-go-type-skip-optional-pointer":true},"self":{"allOf":[{"$ref":"#/components/schemas/CursorString"}],"description":"Cursor pointing to the current page.","x-go-type-skip-optional-pointer":true}},"type":"object","x-go-type-skip-optional-pointer":true},"CursorString":{"example":"aWQ9NTU3LHNlcnZlcktleT1wbDE5Mw==","maxLength":1000,"minLength":1,"type":"string"},"DomainErrorCode":{"description":"* `nil` - This error code is returned when a value can't be nil.\n* `invalid-cursor` - This error code is returned when a cursor can't be decoded (e.g. is malformed).\n* `required` - This error code is returned when a value can't be blank.","enum":["nil","player-not-found","tribe-not-found","village-not-found","invalid-cursor","max-less-equal","required","invalid-url","length-out-of-range","min-greater-equal","server-not-found","sort-conflict","unsupported-sort-string","version-not-found"],"type":"string"},"Ennoblement":{"properties":{"createdAt":{"format":"date-time","type":"string"},"id":{"$ref":"#/components/schemas/IntId"},"newOwner":{"$ref":"#/components/schemas/PlayerMeta"},"points":{"type":"integer"},"village":{"$ref":"#/components/schemas/VillageMeta"}},"required":["id","points","newOwner","village","createdAt"],"type":"object"},"Error":{"additionalProperties":false,"properties":{"code":{"allOf":[{"$ref":"#/components/schemas/DomainErrorCode"},{"enum":["method-not-allowed","route-not-found","internal-server-error","invalid-param-format"],"type":"string"}],"example":"invalid-param-format"},"message":{"type":"string"},"params":{"additionalProperties":true,"description":"Additional data related to the error. Can be used for i18n.","type":"object","x-go-type-skip-optional-pointer":true},"path":{"description":"References field where an error occurred.","items":{"type":"string"},"type":"array","x-go-type-skip-optional-pointer":true}},"required":["code","message"],"type":"object"},"IntId":{"minimum":1,"type":"integer"},"NullPlayerMeta":{"allOf":[{"$ref":"#/components/schemas/PlayerMeta"}],"example":{"id":"1,","name":"string,","profileUrl":"https://en138.tribalwars.net/game.php?screen=info_player\u0026id=11518097,","tribe":{"id":"1,","name":"string,","profileUrl":"https://en138.tribalwars.net/game.php?screen=info_ally\u0026id=13,","tag":"string"}},"nullable":true},"NullTribeMeta":{"allOf":[{"$ref":"#/components/schemas/TribeMeta"}],"example":{"id":1,"name":"string","profileUrl":"https://en138.tribalwars.net/game.php?screen=info_ally\u0026id=13","tag":"string"},"nullable":true},"PaginationResponse":{"properties":{"cursor":{"$ref":"#/components/schemas/Cursor"}},"type":"object"},"Player":{"properties":{"bestRank":{"type":"integer"},"bestRankAt":{"format":"date-time","type":"string"},"createdAt":{"format":"date-time","type":"string"},"deletedAt":{"format":"date-time","type":"string"},"id":{"$ref":"#/components/schemas/IntId"},"lastActivityAt":{"format":"date-time","type":"string"},"mostPoints":{"type":"integer"},"mostPointsAt":{"format":"date-time","type":"string"},"mostVillages":{"type":"integer"},"mostVillagesAt":{"format":"date-time","type":"string"},"name":{"type":"string"},"numVillages":{"type":"integer"},"opponentsDefeated":{"$ref":"#/components/schemas/PlayerOpponentsDefeated"},"points":{"type":"integer"},"profileUrl":{"example":"https://en138.tribalwars.net/game.php?screen=info_player\u0026id=11518097","format":"uri","type":"string"},"rank":{"type":"integer"},"tribe":{"$ref":"#/components/schemas/NullTribeMeta"}},"required":["id","name","rank","points","numVillages","profileUrl","tribe","lastActivityAt","bestRank","bestRankAt","mostPoints","mostPointsAt","mostVillages","mostVillagesAt","opponentsDefeated","createdAt"],"type":"object"},"PlayerMeta":{"properties":{"id":{"$ref":"#/components/schemas/IntId"},"name":{"type":"string"},"profileUrl":{"example":"https://en138.tribalwars.net/game.php?screen=info_player\u0026id=11518097","format":"uri","type":"string"},"tribe":{"$ref":"#/components/schemas/NullTribeMeta"}},"required":["id","name","profileUrl","tribe"],"type":"object"},"PlayerOpponentsDefeated":{"allOf":[{"$ref":"#/components/schemas/TribeOpponentsDefeated"},{"properties":{"rankSup":{"type":"integer"},"scoreSup":{"type":"integer"}},"required":["rankSup","scoreSup"],"type":"object"}],"example":{"rankAtt":"0,","rankDef":"0,","rankSup":0,"rankTotal":"0,","scoreAtt":"0,","scoreDef":"0,","scoreSup":0,"scoreTotal":0}},"PlayerSnapshot":{"properties":{"date":{"format":"date","type":"string"},"id":{"$ref":"#/components/schemas/IntId"},"numVillages":{"type":"integer"},"opponentsDefeated":{"$ref":"#/components/schemas/PlayerOpponentsDefeated"},"player":{"$ref":"#/components/schemas/PlayerMeta"},"points":{"type":"integer"},"rank":{"type":"integer"}},"required":["id","numVillages","points","rank","date","opponentsDefeated","player"],"type":"object"},"PlayerWithServer":{"allOf":[{"$ref":"#/components/schemas/Player"},{"properties":{"server":{"$ref":"#/components/schemas/ServerMeta"}},"required":["server"],"type":"object"}]},"Server":{"properties":{"createdAt":{"format":"date-time","type":"string"},"ennoblementDataSyncedAt":{"format":"date-time","type":"string"},"key":{"$ref":"#/components/schemas/ServerKey"},"numBarbarianVillages":{"type":"integer"},"numBonusVillages":{"type":"integer"},"numPlayerVillages":{"type":"integer"},"numPlayers":{"type":"integer"},"numTribes":{"type":"integer"},"numVillages":{"type":"integer"},"open":{"type":"boolean"},"playerDataSyncedAt":{"format":"date-time","type":"string"},"tribeDataSyncedAt":{"format":"date-time","type":"string"},"url":{"example":"https://en138.tribalwars.net","format":"uri","type":"string"},"villageDataSyncedAt":{"format":"date-time","type":"string"}},"required":["key","open","url","numPlayers","numTribes","numVillages","numBarbarianVillages","numBonusVillages","numPlayerVillages","createdAt"],"type":"object"},"ServerConfig":{"properties":{"ally":{"$ref":"#/components/schemas/ServerConfigAlly"},"build":{"$ref":"#/components/schemas/ServerConfigBuild"},"buildings":{"$ref":"#/components/schemas/ServerConfigBuildings"},"commands":{"$ref":"#/components/schemas/ServerConfigCommands"},"coord":{"$ref":"#/components/schemas/ServerConfigCoord"},"game":{"$ref":"#/components/schemas/ServerConfigGame"},"misc":{"$ref":"#/components/schemas/ServerConfigMisc"},"moral":{"type":"integer"},"newbie":{"$ref":"#/components/schemas/ServerConfigNewbie"},"night":{"$ref":"#/components/schemas/ServerConfigNight"},"sitter":{"$ref":"#/components/schemas/ServerConfigSitter"},"sleep":{"$ref":"#/components/schemas/ServerConfigSleep"},"snob":{"$ref":"#/components/schemas/ServerConfigSnob"},"speed":{"format":"double","type":"number"},"unitSpeed":{"format":"double","type":"number"},"win":{"$ref":"#/components/schemas/ServerConfigWin"}},"required":["ally","build","buildings","commands","coord","game","misc","moral","newbie","night","sitter","sleep","snob","speed","unitSpeed","win"],"type":"object"},"ServerConfigAlly":{"properties":{"allytimeSupport":{"type":"integer"},"fixedAllies":{"type":"integer"},"levels":{"type":"integer"},"limit":{"type":"integer"},"noHarm":{"type":"integer"},"noJoin":{"type":"integer"},"noLeave":{"type":"integer"},"noOtherSupport":{"type":"integer"},"noOtherSupportType":{"type":"integer"},"pointsMemberCount":{"type":"integer"},"warsAutoacceptDays":{"type":"integer"},"warsMemberRequirement":{"type":"integer"},"warsPointsRequirement":{"type":"integer"},"xpRequirements":{"type":"string"}},"required":["allytimeSupport","fixedAllies","levels","limit","noHarm","noJoin","noLeave","noOtherSupport","noOtherSupportType","pointsMemberCount","warsAutoacceptDays","warsMemberRequirement","warsPointsRequirement","xpRequirements"],"type":"object"},"ServerConfigBuild":{"properties":{"destroy":{"type":"integer"}},"required":["destroy"],"type":"object"},"ServerConfigBuildings":{"properties":{"customBarracks":{"type":"integer"},"customChurch":{"type":"integer"},"customFarm":{"type":"integer"},"customGarage":{"type":"integer"},"customHide":{"type":"integer"},"customIron":{"type":"integer"},"customMain":{"type":"integer"},"customMarket":{"type":"integer"},"customPlace":{"type":"integer"},"customSmith":{"type":"integer"},"customSnob":{"type":"integer"},"customStable":{"type":"integer"},"customStatue":{"type":"integer"},"customStone":{"type":"integer"},"customStorage":{"type":"integer"},"customWall":{"type":"integer"},"customWatchtower":{"type":"integer"},"customWood":{"type":"integer"}},"required":["customBarracks","customChurch","customFarm","customGarage","customHide","customIron","customMain","customMarket","customPlace","customSmith","customSnob","customStable","customStatue","customStone","customStorage","customWall","customWatchtower","customWood"],"type":"object"},"ServerConfigCommands":{"properties":{"commandCancelTime":{"type":"integer"},"millisArrival":{"type":"integer"}},"required":["commandCancelTime","millisArrival"],"type":"object"},"ServerConfigCoord":{"properties":{"bonusNew":{"type":"integer"},"bonusVillages":{"type":"integer"},"emptyVillages":{"type":"integer"},"func":{"type":"integer"},"inner":{"type":"integer"},"mapSize":{"type":"integer"},"nobleRestart":{"type":"integer"},"selectStart":{"type":"integer"},"startVillages":{"type":"integer"},"villageMoveWait":{"type":"integer"}},"required":["bonusNew","bonusVillages","emptyVillages","func","inner","mapSize","nobleRestart","selectStart","startVillages","villageMoveWait"],"type":"object"},"ServerConfigGame":{"properties":{"archer":{"type":"integer"},"barbarianMaxPoints":{"type":"integer"},"barbarianRise":{"format":"double","type":"number"},"barbarianShrink":{"type":"integer"},"baseProduction":{"type":"integer"},"buildtimeFormula":{"type":"integer"},"church":{"type":"integer"},"event":{"type":"integer"},"fakeLimit":{"format":"double","type":"number"},"farmLimit":{"type":"integer"},"hauls":{"type":"integer"},"haulsBase":{"type":"integer"},"haulsMax":{"type":"integer"},"knight":{"type":"integer"},"knightNewItems":{"type":"integer"},"scavenging":{"type":"integer"},"stronghold":{"type":"integer"},"suppressEvents":{"type":"integer"},"tech":{"type":"integer"},"watchtower":{"type":"integer"}},"required":["archer","barbarianMaxPoints","barbarianRise","barbarianShrink","baseProduction","buildtimeFormula","church","event","fakeLimit","farmLimit","hauls","haulsBase","haulsMax","knight","knightNewItems","scavenging","stronghold","suppressEvents","tech","watchtower"],"type":"object"},"ServerConfigMisc":{"properties":{"killRanking":{"type":"integer"},"tradeCancelTime":{"type":"integer"},"tutorial":{"type":"integer"}},"required":["killRanking","tradeCancelTime","tutorial"],"type":"object"},"ServerConfigNewbie":{"properties":{"days":{"type":"integer"},"ratio":{"type":"integer"},"ratioDays":{"type":"integer"},"removeNewbieVillages":{"type":"integer"}},"required":["days","ratio","ratioDays","removeNewbieVillages"],"type":"object"},"ServerConfigNight":{"properties":{"active":{"type":"integer"},"defFactor":{"format":"double","type":"number"},"duration":{"type":"integer"},"endHour":{"type":"integer"},"startHour":{"type":"integer"}},"required":["active","defFactor","duration","endHour","startHour"],"type":"object"},"ServerConfigSitter":{"properties":{"allow":{"type":"integer"}},"required":["allow"],"type":"object"},"ServerConfigSleep":{"properties":{"active":{"type":"integer"},"delay":{"type":"integer"},"max":{"type":"integer"},"maxAwake":{"type":"integer"},"min":{"type":"integer"},"minAwake":{"type":"integer"},"warnTime":{"type":"integer"}},"required":["active","delay","max","maxAwake","min","minAwake","warnTime"],"type":"object"},"ServerConfigSnob":{"properties":{"cheapRebuild":{"type":"integer"},"coinIron":{"type":"integer"},"coinStone":{"type":"integer"},"coinWood":{"type":"integer"},"factor":{"format":"double","type":"number"},"gold":{"type":"integer"},"maxDist":{"type":"integer"},"noBarbConquer":{"type":"integer"},"rise":{"type":"integer"}},"required":["cheapRebuild","coinIron","coinStone","coinWood","factor","gold","maxDist","noBarbConquer","rise"],"type":"object"},"ServerConfigWin":{"properties":{"check":{"type":"integer"}},"required":["check"],"type":"object"},"ServerKey":{"example":"en138","maxLength":10,"minLength":1,"type":"string"},"ServerMeta":{"properties":{"key":{"$ref":"#/components/schemas/ServerKey"},"open":{"type":"boolean"},"url":{"example":"https://en138.tribalwars.net","format":"uri","type":"string"}},"required":["key","open","url"],"type":"object"},"Tribe":{"properties":{"allPoints":{"type":"integer"},"bestRank":{"type":"integer"},"bestRankAt":{"format":"date-time","type":"string"},"createdAt":{"format":"date-time","type":"string"},"deletedAt":{"format":"date-time","type":"string"},"dominance":{"format":"double","type":"number"},"id":{"$ref":"#/components/schemas/IntId"},"mostPoints":{"type":"integer"},"mostPointsAt":{"format":"date-time","type":"string"},"mostVillages":{"type":"integer"},"mostVillagesAt":{"format":"date-time","type":"string"},"name":{"type":"string"},"numMembers":{"type":"integer"},"numVillages":{"type":"integer"},"opponentsDefeated":{"$ref":"#/components/schemas/TribeOpponentsDefeated"},"points":{"type":"integer"},"profileUrl":{"example":"https://en138.tribalwars.net/game.php?screen=info_ally\u0026id=13","format":"uri","type":"string"},"rank":{"type":"integer"},"tag":{"type":"string"}},"required":["id","name","tag","profileUrl","points","allPoints","numMembers","numVillages","rank","dominance","opponentsDefeated","bestRank","bestRankAt","mostVillages","mostVillagesAt","mostPoints","mostPointsAt","createdAt"],"type":"object"},"TribeChange":{"properties":{"createdAt":{"format":"date-time","type":"string"},"id":{"$ref":"#/components/schemas/IntId"},"newTribe":{"$ref":"#/components/schemas/NullTribeMeta"},"player":{"$ref":"#/components/schemas/PlayerMeta"}},"required":["id","player","newTribe","createdAt"],"type":"object"},"TribeMeta":{"properties":{"id":{"$ref":"#/components/schemas/IntId"},"name":{"type":"string"},"profileUrl":{"example":"https://en138.tribalwars.net/game.php?screen=info_ally\u0026id=13","format":"uri","type":"string"},"tag":{"type":"string"}},"required":["id","name","tag","profileUrl"],"type":"object"},"TribeOpponentsDefeated":{"properties":{"rankAtt":{"type":"integer"},"rankDef":{"type":"integer"},"rankTotal":{"type":"integer"},"scoreAtt":{"type":"integer"},"scoreDef":{"type":"integer"},"scoreTotal":{"type":"integer"}},"required":["rankAtt","scoreAtt","rankDef","scoreDef","rankTotal","scoreTotal"],"type":"object"},"TribeSnapshot":{"properties":{"allPoints":{"type":"integer"},"date":{"format":"date","type":"string"},"dominance":{"format":"double","type":"number"},"id":{"$ref":"#/components/schemas/IntId"},"numMembers":{"type":"integer"},"numVillages":{"type":"integer"},"opponentsDefeated":{"$ref":"#/components/schemas/TribeOpponentsDefeated"},"points":{"type":"integer"},"rank":{"type":"integer"},"tribe":{"$ref":"#/components/schemas/TribeMeta"}},"required":["id","tribe","allPoints","date","dominance","numMembers","numVillages","points","rank","opponentsDefeated"],"type":"object"},"Unit":{"properties":{"attack":{"type":"integer"},"buildTime":{"format":"double","type":"number"},"carry":{"type":"integer"},"defense":{"type":"integer"},"defenseArcher":{"type":"integer"},"defenseCavalry":{"type":"integer"},"pop":{"type":"integer"},"speed":{"format":"double","type":"number"}},"required":["attack","buildTime","carry","defense","defenseArcher","defenseCavalry","pop","speed"],"type":"object"},"UnitInfo":{"properties":{"archer":{"$ref":"#/components/schemas/Unit"},"axe":{"$ref":"#/components/schemas/Unit"},"catapult":{"$ref":"#/components/schemas/Unit"},"heavy":{"$ref":"#/components/schemas/Unit"},"knight":{"$ref":"#/components/schemas/Unit"},"light":{"$ref":"#/components/schemas/Unit"},"marcher":{"$ref":"#/components/schemas/Unit"},"militia":{"$ref":"#/components/schemas/Unit"},"ram":{"$ref":"#/components/schemas/Unit"},"snob":{"$ref":"#/components/schemas/Unit"},"spear":{"$ref":"#/components/schemas/Unit"},"spy":{"$ref":"#/components/schemas/Unit"},"sword":{"$ref":"#/components/schemas/Unit"}},"required":["archer","axe","catapult","heavy","knight","light","marcher","militia","ram","snob","spear","spy","sword"],"type":"object"},"Version":{"properties":{"code":{"$ref":"#/components/schemas/VersionCode"},"host":{"example":"www.tribalwars.net","format":"hostname","type":"string"},"name":{"example":"International","type":"string"},"timezone":{"example":"Europe/London","type":"string"}},"required":["code","host","name","timezone"],"type":"object"},"VersionCode":{"example":"en","maxLength":2,"minLength":2,"type":"string"},"Village":{"properties":{"bonus":{"description":"Some of the bonuses:\n1 - 100% higher wood production\n2 - 100% higher clay production\n3 - 100% higher iron production\n4 - 10% more population\n5 - 33% faster recruitment in the Barracks\n6 - 33% faster recruitment in the Stable\n7 - 50% faster recruitment in the Workshop\n8 - 30% more resources are produced (all resource types)\n9 - 50% more storage capacity and merchants\n","type":"integer"},"continent":{"example":"K44","type":"string"},"createdAt":{"format":"date-time","type":"string"},"fullName":{"example":"Village (450|450) K44","type":"string"},"id":{"$ref":"#/components/schemas/IntId"},"name":{"example":"Village","type":"string"},"player":{"$ref":"#/components/schemas/NullPlayerMeta"},"points":{"type":"integer"},"profileUrl":{"example":"https://en138.tribalwars.net/game.php?screen=info_village\u0026id=57198","format":"uri","type":"string"},"x":{"example":450,"type":"integer"},"y":{"example":450,"type":"integer"}},"required":["id","name","fullName","x","y","points","profileUrl","continent","bonus","createdAt","player"],"type":"object"},"VillageMeta":{"properties":{"continent":{"example":"K44","type":"string"},"fullName":{"example":"Village (450|450) K44","type":"string"},"id":{"$ref":"#/components/schemas/IntId"},"player":{"$ref":"#/components/schemas/NullPlayerMeta"},"profileUrl":{"example":"https://en138.tribalwars.net/game.php?screen=info_village\u0026id=57198","format":"uri","type":"string"},"x":{"example":450,"type":"integer"},"y":{"example":450,"type":"integer"}},"required":["id","fullName","x","y","profileUrl","continent","player"],"type":"object"},"domain-error-code":{"description":"* `nil` - This error code is returned when a value can't be nil.\n* `invalid-cursor` - This error code is returned when a cursor can't be decoded (e.g. is malformed).\n* `required` - This error code is returned when a value can't be blank.","enum":["nil","player-not-found","tribe-not-found","village-not-found","invalid-cursor","max-less-equal","required","invalid-url","length-out-of-range","min-greater-equal","server-not-found","sort-conflict","unsupported-sort-string","version-not-found"],"type":"string"}}},"info":{"contact":{"email":"contact@twhelp.app","name":"Dawid WysokiƄski","url":"https://dwysokinski.me"},"description":"REST API to interact with [TWHelp](https://twhelp.app).","license":{"name":"MIT"},"title":"TWHelp API","version":"2.0.0"},"openapi":"3.0.0","paths":{"/v2/versions":{"get":{"description":"List versions","operationId":"ListVersions","parameters":[{"$ref":"#/components/parameters/CursorQueryParam"},{"$ref":"#/components/parameters/LimitQueryParam"}],"responses":{"200":{"$ref":"#/components/responses/ListVersionsResponse"},"default":{"$ref":"#/components/responses/ErrorResponse"}},"tags":["versions"]}},"/v2/versions/{versionCode}":{"get":{"description":"Get a version","operationId":"GetVersion","parameters":[{"$ref":"#/components/parameters/VersionCodePathParam"}],"responses":{"200":{"$ref":"#/components/responses/GetVersionResponse"},"default":{"$ref":"#/components/responses/ErrorResponse"}},"tags":["versions"]}},"/v2/versions/{versionCode}/players":{"get":{"description":"List players associated with the given version","operationId":"ListVersionPlayers","parameters":[{"$ref":"#/components/parameters/VersionCodePathParam"},{"$ref":"#/components/parameters/CursorQueryParam"},{"$ref":"#/components/parameters/LimitQueryParam"},{"$ref":"#/components/parameters/PlayerDeletedQueryParam"},{"$ref":"#/components/parameters/PlayerSortQueryParam"},{"$ref":"#/components/parameters/PlayerNameQueryParam"},{"$ref":"#/components/parameters/PlayerIdQueryParam"}],"responses":{"200":{"$ref":"#/components/responses/ListPlayersWithServersResponse"},"default":{"$ref":"#/components/responses/ErrorResponse"}},"tags":["versions","players"]}},"/v2/versions/{versionCode}/servers":{"get":{"description":"List servers associated with the given version","operationId":"ListServers","parameters":[{"$ref":"#/components/parameters/VersionCodePathParam"},{"$ref":"#/components/parameters/CursorQueryParam"},{"$ref":"#/components/parameters/LimitQueryParam"},{"$ref":"#/components/parameters/ServerOpenQueryParam"}],"responses":{"200":{"$ref":"#/components/responses/ListServersResponse"},"default":{"$ref":"#/components/responses/ErrorResponse"}},"tags":["versions","servers"]}},"/v2/versions/{versionCode}/servers/{serverKey}":{"get":{"description":"Get a server","operationId":"GetServer","parameters":[{"$ref":"#/components/parameters/VersionCodePathParam"},{"$ref":"#/components/parameters/ServerKeyPathParam"}],"responses":{"200":{"$ref":"#/components/responses/GetServerResponse"},"default":{"$ref":"#/components/responses/ErrorResponse"}},"tags":["versions","servers"]}},"/v2/versions/{versionCode}/servers/{serverKey}/building-info":{"get":{"description":"Get the given server's building info","operationId":"GetBuildingInfo","parameters":[{"$ref":"#/components/parameters/VersionCodePathParam"},{"$ref":"#/components/parameters/ServerKeyPathParam"}],"responses":{"200":{"$ref":"#/components/responses/GetBuildingInfoResponse"},"default":{"$ref":"#/components/responses/ErrorResponse"}},"tags":["versions","servers"]}},"/v2/versions/{versionCode}/servers/{serverKey}/config":{"get":{"description":"Get the given server's config","operationId":"GetServerConfig","parameters":[{"$ref":"#/components/parameters/VersionCodePathParam"},{"$ref":"#/components/parameters/ServerKeyPathParam"}],"responses":{"200":{"$ref":"#/components/responses/GetServerConfigResponse"},"default":{"$ref":"#/components/responses/ErrorResponse"}},"tags":["versions","servers"]}},"/v2/versions/{versionCode}/servers/{serverKey}/ennoblements":{"get":{"description":"List ennoblements associated with the given server","operationId":"ListEnnoblements","parameters":[{"$ref":"#/components/parameters/VersionCodePathParam"},{"$ref":"#/components/parameters/ServerKeyPathParam"},{"$ref":"#/components/parameters/CursorQueryParam"},{"$ref":"#/components/parameters/LimitQueryParam"},{"$ref":"#/components/parameters/EnnoblementSortQueryParam"},{"$ref":"#/components/parameters/SinceQueryParam"},{"$ref":"#/components/parameters/BeforeQueryParam"}],"responses":{"200":{"$ref":"#/components/responses/ListEnnoblementsResponse"},"default":{"$ref":"#/components/responses/ErrorResponse"}},"tags":["versions","servers","ennoblements"]}},"/v2/versions/{versionCode}/servers/{serverKey}/players":{"get":{"description":"List players associated with the given server","operationId":"ListServerPlayers","parameters":[{"$ref":"#/components/parameters/VersionCodePathParam"},{"$ref":"#/components/parameters/ServerKeyPathParam"},{"$ref":"#/components/parameters/CursorQueryParam"},{"$ref":"#/components/parameters/LimitQueryParam"},{"$ref":"#/components/parameters/PlayerDeletedQueryParam"},{"$ref":"#/components/parameters/PlayerSortQueryParam"},{"$ref":"#/components/parameters/PlayerNameQueryParam"},{"$ref":"#/components/parameters/PlayerIdQueryParam"}],"responses":{"200":{"$ref":"#/components/responses/ListPlayersResponse"},"default":{"$ref":"#/components/responses/ErrorResponse"}},"tags":["versions","servers","players"]}},"/v2/versions/{versionCode}/servers/{serverKey}/players/{playerId}":{"get":{"description":"Get a player","operationId":"GetPlayer","parameters":[{"$ref":"#/components/parameters/VersionCodePathParam"},{"$ref":"#/components/parameters/ServerKeyPathParam"},{"$ref":"#/components/parameters/PlayerIdPathParam"}],"responses":{"200":{"$ref":"#/components/responses/GetPlayerResponse"},"default":{"$ref":"#/components/responses/ErrorResponse"}},"tags":["versions","servers","players"]}},"/v2/versions/{versionCode}/servers/{serverKey}/players/{playerId}/ennoblements":{"get":{"description":"List the given player's ennoblements","operationId":"ListPlayerEnnoblements","parameters":[{"$ref":"#/components/parameters/VersionCodePathParam"},{"$ref":"#/components/parameters/ServerKeyPathParam"},{"$ref":"#/components/parameters/PlayerIdPathParam"},{"$ref":"#/components/parameters/CursorQueryParam"},{"$ref":"#/components/parameters/LimitQueryParam"},{"$ref":"#/components/parameters/EnnoblementSortQueryParam"},{"$ref":"#/components/parameters/SinceQueryParam"},{"$ref":"#/components/parameters/BeforeQueryParam"}],"responses":{"200":{"$ref":"#/components/responses/ListEnnoblementsResponse"},"default":{"$ref":"#/components/responses/ErrorResponse"}},"tags":["versions","servers","players","ennoblements"]}},"/v2/versions/{versionCode}/servers/{serverKey}/players/{playerId}/snapshots":{"get":{"description":"List the given player's snapshots","operationId":"ListPlayerPlayerSnapshots","parameters":[{"$ref":"#/components/parameters/VersionCodePathParam"},{"$ref":"#/components/parameters/ServerKeyPathParam"},{"$ref":"#/components/parameters/PlayerIdPathParam"},{"$ref":"#/components/parameters/CursorQueryParam"},{"$ref":"#/components/parameters/LimitQueryParam"},{"$ref":"#/components/parameters/PlayerSnapshotSortQueryParam"}],"responses":{"200":{"$ref":"#/components/responses/ListPlayerSnapshotsResponse"},"default":{"$ref":"#/components/responses/ErrorResponse"}},"tags":["versions","servers","players","snapshots"]}},"/v2/versions/{versionCode}/servers/{serverKey}/players/{playerId}/tribe-changes":{"get":{"description":"List the given player's tribe changes","operationId":"ListPlayerTribeChanges","parameters":[{"$ref":"#/components/parameters/VersionCodePathParam"},{"$ref":"#/components/parameters/ServerKeyPathParam"},{"$ref":"#/components/parameters/PlayerIdPathParam"},{"$ref":"#/components/parameters/CursorQueryParam"},{"$ref":"#/components/parameters/LimitQueryParam"},{"$ref":"#/components/parameters/TribeChangeSortQueryParam"},{"$ref":"#/components/parameters/SinceQueryParam"},{"$ref":"#/components/parameters/BeforeQueryParam"}],"responses":{"200":{"$ref":"#/components/responses/ListTribeChangesResponse"},"default":{"$ref":"#/components/responses/ErrorResponse"}},"tags":["versions","servers","players","tribe-changes"]}},"/v2/versions/{versionCode}/servers/{serverKey}/players/{playerId}/villages":{"get":{"description":"List the given player's villages","operationId":"ListPlayerVillages","parameters":[{"$ref":"#/components/parameters/VersionCodePathParam"},{"$ref":"#/components/parameters/ServerKeyPathParam"},{"$ref":"#/components/parameters/PlayerIdPathParam"},{"$ref":"#/components/parameters/CursorQueryParam"},{"$ref":"#/components/parameters/LimitQueryParam"}],"responses":{"200":{"$ref":"#/components/responses/ListVillagesResponse"},"default":{"$ref":"#/components/responses/ErrorResponse"}},"tags":["versions","servers","players","villages"]}},"/v2/versions/{versionCode}/servers/{serverKey}/tribes":{"get":{"description":"List tribes associated with the given server","operationId":"ListTribes","parameters":[{"$ref":"#/components/parameters/VersionCodePathParam"},{"$ref":"#/components/parameters/ServerKeyPathParam"},{"$ref":"#/components/parameters/CursorQueryParam"},{"$ref":"#/components/parameters/LimitQueryParam"},{"$ref":"#/components/parameters/TribeDeletedQueryParam"},{"$ref":"#/components/parameters/TribeSortQueryParam"},{"$ref":"#/components/parameters/TribeTagQueryParam"}],"responses":{"200":{"$ref":"#/components/responses/ListTribesResponse"},"default":{"$ref":"#/components/responses/ErrorResponse"}},"tags":["versions","servers","tribes"]}},"/v2/versions/{versionCode}/servers/{serverKey}/tribes/{tribeId}":{"get":{"description":"Get a tribe","operationId":"GetTribe","parameters":[{"$ref":"#/components/parameters/VersionCodePathParam"},{"$ref":"#/components/parameters/ServerKeyPathParam"},{"$ref":"#/components/parameters/TribeIdPathParam"}],"responses":{"200":{"$ref":"#/components/responses/GetTribeResponse"},"default":{"$ref":"#/components/responses/ErrorResponse"}},"tags":["versions","servers","tribes"]}},"/v2/versions/{versionCode}/servers/{serverKey}/tribes/{tribeId}/ennoblements":{"get":{"description":"List the given tribe's ennoblements","operationId":"ListTribeEnnoblements","parameters":[{"$ref":"#/components/parameters/VersionCodePathParam"},{"$ref":"#/components/parameters/ServerKeyPathParam"},{"$ref":"#/components/parameters/TribeIdPathParam"},{"$ref":"#/components/parameters/CursorQueryParam"},{"$ref":"#/components/parameters/LimitQueryParam"},{"$ref":"#/components/parameters/EnnoblementSortQueryParam"},{"$ref":"#/components/parameters/SinceQueryParam"},{"$ref":"#/components/parameters/BeforeQueryParam"}],"responses":{"200":{"$ref":"#/components/responses/ListEnnoblementsResponse"},"default":{"$ref":"#/components/responses/ErrorResponse"}},"tags":["versions","servers","tribes","ennoblements"]}},"/v2/versions/{versionCode}/servers/{serverKey}/tribes/{tribeId}/member-changes":{"get":{"description":"List the given tribe's member changes (who joined, who left, etc.)","operationId":"ListTribeMemberChanges","parameters":[{"$ref":"#/components/parameters/VersionCodePathParam"},{"$ref":"#/components/parameters/ServerKeyPathParam"},{"$ref":"#/components/parameters/TribeIdPathParam"},{"$ref":"#/components/parameters/CursorQueryParam"},{"$ref":"#/components/parameters/LimitQueryParam"},{"$ref":"#/components/parameters/TribeChangeSortQueryParam"},{"$ref":"#/components/parameters/SinceQueryParam"},{"$ref":"#/components/parameters/BeforeQueryParam"}],"responses":{"200":{"$ref":"#/components/responses/ListTribeChangesResponse"},"default":{"$ref":"#/components/responses/ErrorResponse"}},"tags":["versions","servers","tribes","tribe-changes"]}},"/v2/versions/{versionCode}/servers/{serverKey}/tribes/{tribeId}/members":{"get":{"description":"List the given tribe's members","operationId":"ListTribeMembers","parameters":[{"$ref":"#/components/parameters/VersionCodePathParam"},{"$ref":"#/components/parameters/ServerKeyPathParam"},{"$ref":"#/components/parameters/TribeIdPathParam"},{"$ref":"#/components/parameters/CursorQueryParam"},{"$ref":"#/components/parameters/LimitQueryParam"},{"$ref":"#/components/parameters/PlayerSortQueryParam"}],"responses":{"200":{"$ref":"#/components/responses/ListPlayersResponse"},"default":{"$ref":"#/components/responses/ErrorResponse"}},"tags":["versions","servers","tribes","players"]}},"/v2/versions/{versionCode}/servers/{serverKey}/tribes/{tribeId}/snapshots":{"get":{"description":"List the given tribe's snapshots","operationId":"ListTribeTribeSnapshots","parameters":[{"$ref":"#/components/parameters/VersionCodePathParam"},{"$ref":"#/components/parameters/ServerKeyPathParam"},{"$ref":"#/components/parameters/TribeIdPathParam"},{"$ref":"#/components/parameters/CursorQueryParam"},{"$ref":"#/components/parameters/LimitQueryParam"},{"$ref":"#/components/parameters/TribeSnapshotSortQueryParam"}],"responses":{"200":{"$ref":"#/components/responses/ListTribeSnapshotsResponse"},"default":{"$ref":"#/components/responses/ErrorResponse"}},"tags":["versions","servers","tribes","snapshots"]}},"/v2/versions/{versionCode}/servers/{serverKey}/tribes/{tribeId}/villages":{"get":{"description":"List the given tribe's villages","operationId":"ListTribeVillages","parameters":[{"$ref":"#/components/parameters/VersionCodePathParam"},{"$ref":"#/components/parameters/ServerKeyPathParam"},{"$ref":"#/components/parameters/TribeIdPathParam"},{"$ref":"#/components/parameters/CursorQueryParam"},{"$ref":"#/components/parameters/LimitQueryParam"}],"responses":{"200":{"$ref":"#/components/responses/ListVillagesResponse"},"default":{"$ref":"#/components/responses/ErrorResponse"}},"tags":["versions","servers","tribes","villages"]}},"/v2/versions/{versionCode}/servers/{serverKey}/unit-info":{"get":{"description":"Get the given server's unit info","operationId":"GetUnitInfo","parameters":[{"$ref":"#/components/parameters/VersionCodePathParam"},{"$ref":"#/components/parameters/ServerKeyPathParam"}],"responses":{"200":{"$ref":"#/components/responses/GetUnitInfoResponse"},"default":{"$ref":"#/components/responses/ErrorResponse"}},"tags":["versions","servers"]}},"/v2/versions/{versionCode}/servers/{serverKey}/villages":{"get":{"description":"List villages associated with the given server","operationId":"ListVillages","parameters":[{"$ref":"#/components/parameters/VersionCodePathParam"},{"$ref":"#/components/parameters/ServerKeyPathParam"},{"$ref":"#/components/parameters/CursorQueryParam"},{"$ref":"#/components/parameters/LimitQueryParam"},{"$ref":"#/components/parameters/VillageCoordsQueryParam"}],"responses":{"200":{"$ref":"#/components/responses/ListVillagesResponse"},"default":{"$ref":"#/components/responses/ErrorResponse"}},"tags":["versions","servers","villages"]}},"/v2/versions/{versionCode}/servers/{serverKey}/villages/{villageId}":{"get":{"description":"Get a village","operationId":"GetVillage","parameters":[{"$ref":"#/components/parameters/VersionCodePathParam"},{"$ref":"#/components/parameters/ServerKeyPathParam"},{"$ref":"#/components/parameters/VillageIdPathParam"}],"responses":{"200":{"$ref":"#/components/responses/GetVillageResponse"},"default":{"$ref":"#/components/responses/ErrorResponse"}},"tags":["versions","servers","villages"]}},"/v2/versions/{versionCode}/servers/{serverKey}/villages/{villageId}/ennoblements":{"get":{"description":"List the given village's ennoblements","operationId":"ListVillageEnnoblements","parameters":[{"$ref":"#/components/parameters/VersionCodePathParam"},{"$ref":"#/components/parameters/ServerKeyPathParam"},{"$ref":"#/components/parameters/VillageIdPathParam"},{"$ref":"#/components/parameters/CursorQueryParam"},{"$ref":"#/components/parameters/LimitQueryParam"},{"$ref":"#/components/parameters/EnnoblementSortQueryParam"},{"$ref":"#/components/parameters/SinceQueryParam"},{"$ref":"#/components/parameters/BeforeQueryParam"}],"responses":{"200":{"$ref":"#/components/responses/ListEnnoblementsResponse"},"default":{"$ref":"#/components/responses/ErrorResponse"}},"tags":["versions","servers","villages","ennoblements"]}}},"servers":[{"url":"https://twhelp.app/api"},{"url":"https://tribalwarshelp.com/api"}],"tags":[{"description":"Version related endpoints","name":"versions"},{"description":"Server related endpoints","name":"servers"},{"description":"Tribe related endpoints","name":"tribes"},{"description":"Player related endpoints","name":"players"},{"description":"Village related endpoints","name":"villages"},{"description":"Ennoblement (conquer) related endpoints","name":"ennoblements"},{"description":"Tribe change related endpoints","name":"tribe-changes"},{"description":"Snapshot (historical records) related endpoints","name":"snapshots"}]} diff --git a/internal/twhelp/templates/client-with-responses.tmpl b/internal/twhelp/templates/client-with-responses.tmpl new file mode 100644 index 0000000..68f6523 --- /dev/null +++ b/internal/twhelp/templates/client-with-responses.tmpl @@ -0,0 +1,123 @@ +// 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 +} + +{{$clientTypeName := opts.OutputOptions.ClientTypeName -}} + +// WithBaseURL overrides the baseURL. +func WithBaseURL(baseURL string) ClientOption { + return func(c *{{ $clientTypeName }}) 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 { +{{range . -}} +{{$hasParams := .RequiresParamObject -}} +{{$pathParams := .PathParams -}} +{{$opid := .OperationId -}} + // {{$opid}}{{if .HasBody}}WithBody{{end}}WithResponse request{{if .HasBody}} with any body{{end}} + {{$opid}}{{if .HasBody}}WithBody{{end}}WithResponse(ctx context.Context{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params *{{$opid}}Params{{end}}{{if .HasBody}}, contentType string, body io.Reader{{end}}, reqEditors... RequestEditorFn) (*{{genResponseTypeName $opid}}Container, error) +{{range .Bodies}} + {{if .IsSupportedByClient -}} + {{$opid}}{{.Suffix}}WithResponse(ctx context.Context{{genParamArgs $pathParams}}{{if $hasParams}}, params *{{$opid}}Params{{end}}, body {{$opid}}{{.NameTag}}RequestBody, reqEditors... RequestEditorFn) (*{{genResponseTypeName $opid}}Container, error) + {{end -}} +{{end}}{{/* range .Bodies */}} +{{end}}{{/* range . $opid := .OperationId */}} +} + +{{range .}}{{$opid := .OperationId}}{{$op := .}} +type {{genResponseTypeName $opid | ucFirst}}Container struct { + Body []byte + HTTPResponse *http.Response + {{- range getResponseTypeDefinitions .}} + {{.TypeName}} *{{.Schema.TypeDecl}} + {{- end}} +} + +// Status returns HTTPResponse.Status +func (r {{genResponseTypeName $opid | ucFirst}}Container) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r {{genResponseTypeName $opid | ucFirst}}Container) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} +{{end}} + + +{{range .}} +{{$opid := .OperationId -}} +{{/* Generate client methods (with responses)*/}} + +// {{$opid}}{{if .HasBody}}WithBody{{end}}WithResponse request{{if .HasBody}} with arbitrary body{{end}} returning *{{genResponseTypeName $opid}}Container +func (c *ClientWithResponses) {{$opid}}{{if .HasBody}}WithBody{{end}}WithResponse(ctx context.Context{{genParamArgs .PathParams}}{{if .RequiresParamObject}}, params *{{$opid}}Params{{end}}{{if .HasBody}}, contentType string, body io.Reader{{end}}, reqEditors... RequestEditorFn) (*{{genResponseTypeName $opid}}Container, error){ + rsp, err := c.{{$opid}}{{if .HasBody}}WithBody{{end}}(ctx{{genParamNames .PathParams}}{{if .RequiresParamObject}}, params{{end}}{{if .HasBody}}, contentType, body{{end}}, reqEditors...) + if err != nil { + return nil, err + } + return Parse{{genResponseTypeName $opid | ucFirst}}Container(rsp) +} + +{{$hasParams := .RequiresParamObject -}} +{{$pathParams := .PathParams -}} +{{$bodyRequired := .BodyRequired -}} +{{range .Bodies}} +{{if .IsSupportedByClient -}} +func (c *ClientWithResponses) {{$opid}}{{.Suffix}}WithResponse(ctx context.Context{{genParamArgs $pathParams}}{{if $hasParams}}, params *{{$opid}}Params{{end}}, body {{$opid}}{{.NameTag}}RequestBody, reqEditors... RequestEditorFn) (*{{genResponseTypeName $opid}}Container, error) { + rsp, err := c.{{$opid}}{{.Suffix}}(ctx{{genParamNames $pathParams}}{{if $hasParams}}, params{{end}}, body, reqEditors...) + if err != nil { + return nil, err + } + return Parse{{genResponseTypeName $opid | ucFirst}}Container(rsp) +} +{{end}} +{{end}} + +{{end}}{{/* operations */}} + +{{/* Generate parse functions for responses*/}} +{{range .}}{{$opid := .OperationId}} + +// Parse{{genResponseTypeName $opid | ucFirst}} parses an HTTP response from a {{$opid}}WithResponse call +func Parse{{genResponseTypeName $opid | ucFirst}}Container(rsp *http.Response) (*{{genResponseTypeName $opid}}Container, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &{{genResponseTypeName $opid | ucFirst}}Container{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + {{genResponseUnmarshal .}} + + return response, nil +} +{{end}}{{/* range . $opid := .OperationId */}} diff --git a/internal/twhelp/twhelp.go b/internal/twhelp/twhelp.go index 9ae8431..4b75718 100644 --- a/internal/twhelp/twhelp.go +++ b/internal/twhelp/twhelp.go @@ -1,181 +1,29 @@ package twhelp import ( - "encoding/json" - "time" + "context" + "net/http" + + "golang.org/x/time/rate" ) -type NullBool struct { - Bool bool - Valid bool // Valid is true if Bool is not NULL -} +//go:generate oapi-codegen -templates templates/ --config=config.yml openapi3.json -type ErrorCode string - -const ( - ErrorCodeInternalServerError ErrorCode = "internal-server-error" - ErrorCodeEntityNotFound ErrorCode = "entity-not-found" - ErrorCodeValidationError ErrorCode = "validation-error" - ErrorCodeRouteNotFound ErrorCode = "route-not-found" - ErrorCodeMethodNotAllowed ErrorCode = "method-not-allowed" -) - -func (c ErrorCode) String() string { - return string(c) -} - -type APIError struct { - Code ErrorCode `json:"code"` - Message string `json:"message"` -} - -func (e APIError) Error() string { - return e.Message + " (code=" + e.Code.String() + ")" -} - -type errorResp struct { - Error APIError `json:"error"` -} - -type Version struct { - Code string `json:"code"` - Host string `json:"host"` - Name string `json:"name"` - Timezone string `json:"timezone"` -} - -type listVersionsResp struct { - Data []Version `json:"data"` -} - -type Server struct { - Key string `json:"key"` - URL string `json:"url"` - Open bool `json:"open"` -} - -type getServerResp struct { - Data Server `json:"data"` -} - -type listServersResp struct { - Data []Server `json:"data"` -} - -type Tribe struct { - ID int64 `json:"id"` - Tag string `json:"tag"` - Name string `json:"name"` - ProfileURL string `json:"profileUrl"` - DeletedAt time.Time `json:"deletedAt"` -} - -type listTribesResp struct { - Data []Tribe `json:"data"` -} - -type getTribeResp struct { - Data Tribe `json:"data"` -} - -type TribeMeta struct { - ID int64 `json:"id"` - Name string `json:"name"` - Tag string `json:"tag"` - ProfileURL string `json:"profileUrl"` -} - -type NullTribeMeta struct { - Tribe TribeMeta - Valid bool -} - -func (t NullTribeMeta) MarshalJSON() ([]byte, error) { - if !t.Valid { - return []byte("null"), nil - } - - return json.Marshal(t.Tribe) -} - -func (t *NullTribeMeta) UnmarshalJSON(data []byte) error { - // Ignore null, like in the main JSON package. - if string(data) == "null" { +func WithUserAgent(userAgent string) ClientOption { + return func(client *Client) error { + client.RequestEditors = append(client.RequestEditors, func(_ context.Context, req *http.Request) error { + req.Header.Set("User-Agent", userAgent) + return nil + }) return nil } - - if err := json.Unmarshal(data, &t.Tribe); err != nil { - return err - } - - t.Valid = true - - return nil } -type PlayerMeta struct { - ID int64 `json:"id"` - Name string `json:"name"` - ProfileURL string `json:"profileUrl"` - Tribe NullTribeMeta `json:"tribe"` -} - -type NullPlayerMeta struct { - Player PlayerMeta - Valid bool -} - -func (p NullPlayerMeta) MarshalJSON() ([]byte, error) { - if !p.Valid { - return []byte("null"), nil - } - - return json.Marshal(p.Player) -} - -func (p *NullPlayerMeta) UnmarshalJSON(data []byte) error { - // Ignore null, like in the main JSON package. - if string(data) == "null" { +func WithRateLimiter(rl *rate.Limiter) ClientOption { + return func(client *Client) error { + client.RequestEditors = append(client.RequestEditors, func(ctx context.Context, _ *http.Request) error { + return rl.Wait(ctx) + }) return nil } - - if err := json.Unmarshal(data, &p.Player); err != nil { - return err - } - - p.Valid = true - - return nil -} - -type Village struct { - ID int64 `json:"id"` - FullName string `json:"fullName"` - ProfileURL string `json:"profileUrl"` - Points int64 `json:"points"` - X int64 `json:"x"` - Y int64 `json:"y"` - Player NullPlayerMeta `json:"player"` -} - -type listVillagesResp struct { - Data []Village `json:"data"` -} - -type VillageMeta struct { - ID int64 `json:"id"` - FullName string `json:"fullName"` - ProfileURL string `json:"profileUrl"` - Player NullPlayerMeta `json:"player"` -} - -type Ennoblement struct { - ID int64 `json:"id"` - Village VillageMeta `json:"village"` - NewOwner NullPlayerMeta `json:"newOwner"` - CreatedAt time.Time `json:"createdAt"` -} - -type listEnnoblementsResp struct { - Data []Ennoblement `json:"data"` } diff --git a/internal/twhelp/twhelp_test.go b/internal/twhelp/twhelp_test.go deleted file mode 100644 index 567ea32..0000000 --- a/internal/twhelp/twhelp_test.go +++ /dev/null @@ -1,258 +0,0 @@ -package twhelp_test - -import ( - "encoding/json" - "testing" - - "gitea.dwysokinski.me/twhelp/dcbot/internal/twhelp" - "github.com/stretchr/testify/assert" -) - -func TestNullTribeMeta_MarshalJSON(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - tribe twhelp.NullTribeMeta - expectedJSON string - }{ - { - name: "OK: null 1", - tribe: twhelp.NullTribeMeta{ - Tribe: twhelp.TribeMeta{}, - Valid: false, - }, - expectedJSON: "null", - }, - { - name: "OK: null 2", - tribe: twhelp.NullTribeMeta{ - Tribe: twhelp.TribeMeta{ID: 1234}, - Valid: false, - }, - expectedJSON: "null", - }, - { - name: "OK: valid struct", - tribe: twhelp.NullTribeMeta{ - Tribe: twhelp.TribeMeta{ - ID: 997, - Name: "name 997", - Tag: "tag 997", - ProfileURL: "profile-997", - }, - Valid: true, - }, - expectedJSON: `{"id":997,"name":"name 997","tag":"tag 997","profileUrl":"profile-997"}`, - }, - } - - for _, tt := range tests { - tt := tt - - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - - b, err := json.Marshal(tt.tribe) - assert.NoError(t, err) - assert.JSONEq(t, tt.expectedJSON, string(b)) - }) - } -} - -func TestNullTribeMeta_UnmarshalJSON(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - json string - expectedTribe twhelp.NullTribeMeta - expectedJSONSyntaxError bool - }{ - { - name: "OK: null", - json: "null", - expectedTribe: twhelp.NullTribeMeta{ - Tribe: twhelp.TribeMeta{}, - Valid: false, - }, - }, - { - name: "OK: valid struct", - //nolint:lll - json: `{"id":997,"name":"name 997","tag":"tag 997","profileUrl":"profile-997"}`, - expectedTribe: twhelp.NullTribeMeta{ - Tribe: twhelp.TribeMeta{ - ID: 997, - Name: "name 997", - Tag: "tag 997", - ProfileURL: "profile-997", - }, - Valid: true, - }, - }, - { - name: "ERR: invalid tribe 1", - json: "2022-07-30T14:13:12.0000005Z", - expectedTribe: twhelp.NullTribeMeta{}, - expectedJSONSyntaxError: true, - }, - { - name: "ERR: invalid tribe 2", - json: "hello world", - expectedTribe: twhelp.NullTribeMeta{}, - expectedJSONSyntaxError: true, - }, - } - - for _, tt := range tests { - tt := tt - - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - - var target twhelp.NullTribeMeta - err := json.Unmarshal([]byte(tt.json), &target) - if tt.expectedJSONSyntaxError { - var syntaxError *json.SyntaxError - assert.ErrorAs(t, err, &syntaxError) - } else { - assert.NoError(t, err) - } - assert.Equal(t, tt.expectedTribe, target) - }) - } -} - -func TestNullPlayerMeta_MarshalJSON(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - player twhelp.NullPlayerMeta - expectedJSON string - }{ - { - name: "OK: null 1", - player: twhelp.NullPlayerMeta{ - Player: twhelp.PlayerMeta{}, - Valid: false, - }, - expectedJSON: "null", - }, - { - name: "OK: null 2", - player: twhelp.NullPlayerMeta{ - Player: twhelp.PlayerMeta{ID: 1234}, - Valid: false, - }, - expectedJSON: "null", - }, - { - name: "OK: valid struct", - player: twhelp.NullPlayerMeta{ - Player: twhelp.PlayerMeta{ - ID: 997, - Name: "name 997", - ProfileURL: "profile-997", - Tribe: twhelp.NullTribeMeta{ - Valid: true, - Tribe: twhelp.TribeMeta{ - ID: 1234, - Name: "name 997", - ProfileURL: "profile-997", - Tag: "tag 997", - }, - }, - }, - Valid: true, - }, - //nolint:lll - expectedJSON: `{"id":997,"name":"name 997","profileUrl":"profile-997","tribe":{"id":1234,"name":"name 997","profileUrl":"profile-997","tag":"tag 997"}}`, - }, - } - - for _, tt := range tests { - tt := tt - - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - - b, err := json.Marshal(tt.player) - assert.NoError(t, err) - assert.JSONEq(t, tt.expectedJSON, string(b)) - }) - } -} - -func TestNullPlayerMeta_UnmarshalJSON(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - json string - expectedPlayer twhelp.NullPlayerMeta - expectedJSONSyntaxError bool - }{ - { - name: "OK: null", - json: "null", - expectedPlayer: twhelp.NullPlayerMeta{ - Player: twhelp.PlayerMeta{}, - Valid: false, - }, - }, - { - name: "OK: valid struct", - //nolint:lll - json: `{"id":997,"name":"name 997","profileUrl":"profile-997","tribe":{"id":1234,"name":"name 997","profileUrl":"profile-997","tag":"tag 997"}}`, - expectedPlayer: twhelp.NullPlayerMeta{ - Player: twhelp.PlayerMeta{ - ID: 997, - Name: "name 997", - ProfileURL: "profile-997", - Tribe: twhelp.NullTribeMeta{ - Valid: true, - Tribe: twhelp.TribeMeta{ - ID: 1234, - Name: "name 997", - ProfileURL: "profile-997", - Tag: "tag 997", - }, - }, - }, - Valid: true, - }, - }, - { - name: "ERR: invalid tribe 1", - json: "2022-07-30T14:13:12.0000005Z", - expectedPlayer: twhelp.NullPlayerMeta{}, - expectedJSONSyntaxError: true, - }, - { - name: "ERR: invalid tribe 2", - json: "hello world", - expectedPlayer: twhelp.NullPlayerMeta{}, - expectedJSONSyntaxError: true, - }, - } - - for _, tt := range tests { - tt := tt - - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - - var target twhelp.NullPlayerMeta - err := json.Unmarshal([]byte(tt.json), &target) - if tt.expectedJSONSyntaxError { - var syntaxError *json.SyntaxError - assert.ErrorAs(t, err, &syntaxError) - } else { - assert.NoError(t, err) - } - assert.Equal(t, tt.expectedPlayer, target) - }) - } -} diff --git a/k8s/overlays/dev/bot.yml b/k8s/overlays/dev/bot.yml index 4d44552..2274a94 100644 --- a/k8s/overlays/dev/bot.yml +++ b/k8s/overlays/dev/bot.yml @@ -31,7 +31,7 @@ spec: name: twhelp-dcbot-secret key: token - name: TWHELP_URL - value: "https://tribalwarshelp.com" + value: "https://twhelp.app/api" - name: TWHELP_RATE_LIMITER_ENABLED value: "true" - name: TWHELP_RATE_LIMITER_MAX_REQUESTS_PER_SECOND diff --git a/k8s/overlays/prod/bot.yml b/k8s/overlays/prod/bot.yml index c4492d7..8497ee3 100644 --- a/k8s/overlays/prod/bot.yml +++ b/k8s/overlays/prod/bot.yml @@ -31,7 +31,7 @@ spec: name: twhelp-dcbot-secret key: token - name: TWHELP_URL - value: "http://twhelp-api-service:9234" + value: "http://twhelp-api-service:9234/api" - name: BOT_MAX_GROUPS_PER_SERVER value: "5" - name: BOT_MAX_MONITORS_PER_GROUP diff --git a/skaffold.yml b/skaffold.yml index 6a13b31..671cd38 100644 --- a/skaffold.yml +++ b/skaffold.yml @@ -4,6 +4,9 @@ build: tagPolicy: customTemplate: template: latest + local: + useBuildkit: true + useDockerCLI: true artifacts: - image: dcbot hooks: