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/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/twhelp_v2_http.go b/internal/adapter/twhelp_v2_http.go new file mode 100644 index 0000000..b5df297 --- /dev/null +++ b/internal/adapter/twhelp_v2_http.go @@ -0,0 +1,297 @@ +package adapter + +import ( + "context" + "errors" + "net/http" + "time" + + "gitea.dwysokinski.me/twhelp/dcbot/internal/domain" + "gitea.dwysokinski.me/twhelp/dcbot/internal/twhelp" + v2 "gitea.dwysokinski.me/twhelp/dcbot/internal/twhelp/v2" +) + +type TWHelpV2HTTP struct { + client *v2.ClientWithResponses +} + +func NewTWHelpV2HTTP(baseURL string, userAgent string, httpClient *http.Client) (*TWHelpV2HTTP, error) { + client, err := v2.NewClientWithResponses( + "", + v2.WithBaseURL(baseURL), + v2.WithHTTPClient(httpClient), + v2.WithRequestEditorFn(func(_ context.Context, req *http.Request) error { + req.Header.Set("User-Agent", userAgent) + return nil + }), + ) + if err != nil { + return nil, err + } + + return &TWHelpV2HTTP{client: client}, nil +} + +func (t *TWHelpV2HTTP) ListVersions(ctx context.Context) ([]domain.TWVersion, error) { + versions, err := t.client.ListVersionsWithResponse(ctx) + if err != nil { + return nil, err + } + + res := make([]domain.TWVersion, 0, len(versions)) + for _, v := range versions { + res = append(res, t.convertVersionToDomain(v)) + } + + return res, nil +} + +func (t *TWHelpV2HTTP) 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 *TWHelpV2HTTP) 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 *TWHelpV2HTTP) listServers(ctx context.Context, version string, params twhelp.ListServersQueryParams) ([]domain.TWServer, error) { + servers, err := t.client.ListServers(ctx, version, params) + if err != nil { + return nil, err + } + + res := make([]domain.TWServer, 0, len(servers)) + for _, s := range servers { + res = append(res, t.convertServerToDomain(s)) + } + + return res, nil +} + +func (t *TWHelpV2HTTP) GetOpenServer(ctx context.Context, versionCode, serverKey string) (domain.TWServer, error) { + server, err := t.GetServer(ctx, versionCode, serverKey) + if err != nil { + return domain.TWServer{}, err + } + if !server.Open { + return domain.TWServer{}, domain.TWServerNotFoundError{ + VersionCode: versionCode, + Key: serverKey, + } + } + return server, nil +} + +func (t *TWHelpV2HTTP) GetServer(ctx context.Context, versionCode, serverKey string) (domain.TWServer, error) { + server, err := t.client.GetServer(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 t.convertServerToDomain(server), nil +} + +func (t *TWHelpV2HTTP) GetTribeByID(ctx context.Context, versionCode, serverKey string, id int64) (domain.Tribe, error) { + tribe, err := t.client.GetTribeByID(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 t.convertTribeToDomain(tribe), nil +} + +func (t *TWHelpV2HTTP) 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, + }, + }) + if err != nil { + return domain.Tribe{}, err + } + + if len(tribes) == 0 { + return domain.Tribe{}, domain.TribeTagNotFoundError{ + VersionCode: versionCode, + ServerKey: serverKey, + Tag: tribeTag, + } + } + + return t.convertTribeToDomain(tribes[0]), nil +} + +func (t *TWHelpV2HTTP) 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, + }, + }) + if err != nil { + return nil, err + } + + res := make([]domain.Tribe, 0, len(tribes)) + for _, tr := range tribes { + res = append(res, t.convertTribeToDomain(tr)) + } + + return res, nil +} + +func (t *TWHelpV2HTTP) ListVillagesByCoords( + ctx context.Context, + versionCode, serverKey string, + coords []string, + offset, limit int32, +) ([]domain.Village, error) { + villages, err := t.client.ListVillages(ctx, versionCode, serverKey, twhelp.ListVillagesQueryParams{ + Limit: limit, + Offset: offset, + Coords: coords, + }) + if err != nil { + return nil, err + } + + res := make([]domain.Village, 0, len(villages)) + for _, v := range villages { + res = append(res, t.convertVillageToDomain(v)) + } + + return res, nil +} + +func (t *TWHelpV2HTTP) ListEnnoblementsSince( + ctx context.Context, + versionCode, serverKey string, + since time.Time, + offset, limit int32, +) ([]domain.Ennoblement, error) { + ennoblements, err := t.client.ListEnnoblements(ctx, versionCode, serverKey, twhelp.ListEnnoblementsQueryParams{ + Limit: limit, + Offset: offset, + Since: since, + Sort: []twhelp.EnnoblementSort{ + twhelp.EnnoblementSortCreatedAtASC, + }, + }) + if err != nil { + return nil, err + } + + res := make([]domain.Ennoblement, 0, len(ennoblements)) + for _, e := range ennoblements { + res = append(res, t.convertEnnoblementToDomain(e)) + } + + return res, nil +} + +func (t *TWHelpV2HTTP) convertVersionToDomain(v twhelp.Version) domain.TWVersion { + return domain.TWVersion(v) +} + +func (t *TWHelpV2HTTP) convertServerToDomain(s twhelp.Server) domain.TWServer { + return domain.TWServer(s) +} + +func (t *TWHelpV2HTTP) convertTribeToDomain(tr twhelp.Tribe) domain.Tribe { + return domain.Tribe(tr) +} + +func (t *TWHelpV2HTTP) convertTribeMetaToDomain(tr twhelp.TribeMeta) domain.TribeMeta { + return domain.TribeMeta(tr) +} + +func (t *TWHelpV2HTTP) convertNullTribeMetaToDomain(tr twhelp.NullTribeMeta) domain.NullTribeMeta { + return domain.NullTribeMeta{ + Tribe: t.convertTribeMetaToDomain(tr.Tribe), + Valid: tr.Valid, + } +} + +func (t *TWHelpV2HTTP) convertPlayerMetaToDomain(p twhelp.PlayerMeta) domain.PlayerMeta { + return domain.PlayerMeta{ + ID: p.ID, + Name: p.Name, + ProfileURL: p.ProfileURL, + Tribe: t.convertNullTribeMetaToDomain(p.Tribe), + } +} + +func (t *TWHelpV2HTTP) convertNullPlayerMetaToDomain(p twhelp.NullPlayerMeta) domain.NullPlayerMeta { + return domain.NullPlayerMeta{ + Player: t.convertPlayerMetaToDomain(p.Player), + Valid: p.Valid, + } +} + +func (t *TWHelpV2HTTP) convertVillageToDomain(v twhelp.Village) domain.Village { + return domain.Village{ + ID: v.ID, + FullName: v.FullName, + ProfileURL: v.ProfileURL, + Points: v.Points, + X: v.X, + Y: v.Y, + Player: t.convertNullPlayerMetaToDomain(v.Player), + } +} + +func (t *TWHelpV2HTTP) convertVillageMetaToDomain(v twhelp.VillageMeta) domain.VillageMeta { + return domain.VillageMeta{ + ID: v.ID, + FullName: v.FullName, + ProfileURL: v.ProfileURL, + Player: t.convertNullPlayerMetaToDomain(v.Player), + } +} + +func (t *TWHelpV2HTTP) convertEnnoblementToDomain(e twhelp.Ennoblement) domain.Ennoblement { + return domain.Ennoblement{ + ID: e.ID, + Village: t.convertVillageMetaToDomain(e.Village), + NewOwner: t.convertNullPlayerMetaToDomain(e.NewOwner), + CreatedAt: e.CreatedAt, + } +} diff --git a/internal/twhelp/v2/.gitignore b/internal/twhelp/v2/.gitignore new file mode 100644 index 0000000..bc7b52c --- /dev/null +++ b/internal/twhelp/v2/.gitignore @@ -0,0 +1 @@ +*.gen.go diff --git a/internal/twhelp/v2/config.yml b/internal/twhelp/v2/config.yml new file mode 100644 index 0000000..3f0efed --- /dev/null +++ b/internal/twhelp/v2/config.yml @@ -0,0 +1,5 @@ +package: v2 +generate: + models: true + client: true +output: twhelp.gen.go diff --git a/internal/twhelp/v2/openapi3.json b/internal/twhelp/v2/openapi3.json new file mode 100644 index 0000000..f65a5f7 --- /dev/null +++ b/internal/twhelp/v2/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/v2/templates/client-with-responses.tmpl b/internal/twhelp/v2/templates/client-with-responses.tmpl new file mode 100644 index 0000000..68f6523 --- /dev/null +++ b/internal/twhelp/v2/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/v2/twhelp.go b/internal/twhelp/v2/twhelp.go new file mode 100644 index 0000000..86f2bed --- /dev/null +++ b/internal/twhelp/v2/twhelp.go @@ -0,0 +1,3 @@ +package v2 + +//go:generate oapi-codegen -templates templates/ --config=config.yml openapi3.json