Pg to bun (#148)

* start moving to bun

* changing more stuff

* more

* and yet more

* tests passing

* seems stable now

* more big changes

* small fix

* little fixes
This commit is contained in:
tobi 2021-08-25 15:34:33 +02:00 committed by GitHub
parent 071eca20ce
commit 2dc9fc1626
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
713 changed files with 98694 additions and 22704 deletions

View file

@ -129,10 +129,10 @@ The following libraries and frameworks are used by GoToSocial, with gratitude
* [gin-contrib/static](https://github.com/gin-contrib/static); Gin static page middleware. [MIT License](https://spdx.org/licenses/MIT.html) * [gin-contrib/static](https://github.com/gin-contrib/static); Gin static page middleware. [MIT License](https://spdx.org/licenses/MIT.html)
* [go-fed/activity](https://github.com/go-fed/activity); Golang ActivityPub/ActivityStreams library. [BSD-3-Clause License](https://spdx.org/licenses/BSD-3-Clause.html). * [go-fed/activity](https://github.com/go-fed/activity); Golang ActivityPub/ActivityStreams library. [BSD-3-Clause License](https://spdx.org/licenses/BSD-3-Clause.html).
* [go-fed/httpsig](https://github.com/go-fed/httpsig); secure HTTP signature library. [BSD-3-Clause License](https://spdx.org/licenses/BSD-3-Clause.html). * [go-fed/httpsig](https://github.com/go-fed/httpsig); secure HTTP signature library. [BSD-3-Clause License](https://spdx.org/licenses/BSD-3-Clause.html).
* [go-pg/pg](https://github.com/go-pg/pg); Postgres ORM library. [BSD-2-Clause License](https://spdx.org/licenses/BSD-2-Clause.html).
* [google/uuid](https://github.com/google/uuid); UUID generation. [BSD-3-Clause License](https://spdx.org/licenses/BSD-3-Clause.html) * [google/uuid](https://github.com/google/uuid); UUID generation. [BSD-3-Clause License](https://spdx.org/licenses/BSD-3-Clause.html)
* [gorilla/websocket](https://github.com/gorilla/websocket); Websocket connectivity. [BSD-2-Clause License](https://spdx.org/licenses/BSD-2-Clause.html). * [gorilla/websocket](https://github.com/gorilla/websocket); Websocket connectivity. [BSD-2-Clause License](https://spdx.org/licenses/BSD-2-Clause.html).
* [h2non/filetype](https://github.com/h2non/filetype); filetype checking. [MIT License](https://spdx.org/licenses/MIT.html). * [h2non/filetype](https://github.com/h2non/filetype); filetype checking. [MIT License](https://spdx.org/licenses/MIT.html).
* [jackc/pgx](https://github.com/jackc/pgx); Postgres driver. [MIT License](https://spdx.org/licenses/MIT.html).
* [microcosm-cc/bluemonday](https://github.com/microcosm-cc/bluemonday); HTML user-input sanitization. [BSD-3-Clause License](https://spdx.org/licenses/BSD-3-Clause.html). * [microcosm-cc/bluemonday](https://github.com/microcosm-cc/bluemonday); HTML user-input sanitization. [BSD-3-Clause License](https://spdx.org/licenses/BSD-3-Clause.html).
* [mvdan/xurls](https://github.com/mvdan/xurls); URL parsing regular expressions. [BSD-3-Clause License](https://spdx.org/licenses/BSD-3-Clause.html). * [mvdan/xurls](https://github.com/mvdan/xurls); URL parsing regular expressions. [BSD-3-Clause License](https://spdx.org/licenses/BSD-3-Clause.html).
* [nfnt/resize](https://github.com/nfnt/resize); convenient image resizing. [ISC License](https://spdx.org/licenses/ISC.html). * [nfnt/resize](https://github.com/nfnt/resize); convenient image resizing. [ISC License](https://spdx.org/licenses/ISC.html).
@ -145,6 +145,7 @@ The following libraries and frameworks are used by GoToSocial, with gratitude
* [superseriousbusiness/oauth2](https://github.com/superseriousbusiness/oauth2) forked from [go-oauth2/oauth2](https://github.com/go-oauth2/oauth2); oauth server framework and token handling. [MIT License](https://spdx.org/licenses/MIT.html). * [superseriousbusiness/oauth2](https://github.com/superseriousbusiness/oauth2) forked from [go-oauth2/oauth2](https://github.com/go-oauth2/oauth2); oauth server framework and token handling. [MIT License](https://spdx.org/licenses/MIT.html).
* [go-swagger/go-swagger](https://github.com/go-swagger/go-swagger); Swagger OpenAPI spec generation. [Apache-2.0 License](https://spdx.org/licenses/Apache-2.0.html). * [go-swagger/go-swagger](https://github.com/go-swagger/go-swagger); Swagger OpenAPI spec generation. [Apache-2.0 License](https://spdx.org/licenses/Apache-2.0.html).
* [tdewolff/minify](https://github.com/tdewolff/minify); HTML minification. [MIT License](https://spdx.org/licenses/MIT.html). * [tdewolff/minify](https://github.com/tdewolff/minify); HTML minification. [MIT License](https://spdx.org/licenses/MIT.html).
* [uptrace/bun](https://github.com/uptrace/bun); database ORM. [BSD-2-Clause License](https://spdx.org/licenses/BSD-2-Clause.html).
* [urfave/cli](https://github.com/urfave/cli); command-line interface framework. [MIT License](https://spdx.org/licenses/MIT.html). * [urfave/cli](https://github.com/urfave/cli); command-line interface framework. [MIT License](https://spdx.org/licenses/MIT.html).
* [wagslane/go-password-validator](https://github.com/wagslane/go-password-validator); password strength validation. [MIT License](https://spdx.org/licenses/MIT.html). * [wagslane/go-password-validator](https://github.com/wagslane/go-password-validator); password strength validation. [MIT License](https://spdx.org/licenses/MIT.html).

9
go.mod
View file

@ -21,15 +21,15 @@ require (
github.com/go-errors/errors v1.4.0 // indirect github.com/go-errors/errors v1.4.0 // indirect
github.com/go-fed/activity v1.0.1-0.20210803212804-d866ba75dd0f github.com/go-fed/activity v1.0.1-0.20210803212804-d866ba75dd0f
github.com/go-fed/httpsig v1.1.0 github.com/go-fed/httpsig v1.1.0
github.com/go-pg/pg/extra/pgdebug v0.2.0
github.com/go-pg/pg/v10 v10.10.3
github.com/go-playground/validator/v10 v10.7.0 // indirect github.com/go-playground/validator/v10 v10.7.0 // indirect
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 // indirect github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 // indirect
github.com/golang/mock v1.6.0 // indirect github.com/golang/mock v1.6.0 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/uuid v1.3.0 github.com/google/uuid v1.3.0
github.com/gorilla/sessions v1.2.1 // indirect github.com/gorilla/sessions v1.2.1 // indirect
github.com/gorilla/websocket v1.4.2 github.com/gorilla/websocket v1.4.2
github.com/h2non/filetype v1.1.1 github.com/h2non/filetype v1.1.1
github.com/jackc/pgx/v4 v4.13.0
github.com/json-iterator/go v1.1.11 // indirect github.com/json-iterator/go v1.1.11 // indirect
github.com/leodido/go-urn v1.2.1 // indirect github.com/leodido/go-urn v1.2.1 // indirect
github.com/mattn/go-isatty v0.0.13 // indirect github.com/mattn/go-isatty v0.0.13 // indirect
@ -38,7 +38,6 @@ require (
github.com/modern-go/reflect2 v1.0.1 // indirect github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
github.com/oklog/ulid v1.3.1 github.com/oklog/ulid v1.3.1
github.com/onsi/gomega v1.14.0 // indirect
github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b // indirect github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b // indirect
github.com/russross/blackfriday/v2 v2.1.0 github.com/russross/blackfriday/v2 v2.1.0
github.com/sirupsen/logrus v1.8.1 github.com/sirupsen/logrus v1.8.1
@ -47,13 +46,15 @@ require (
github.com/superseriousbusiness/oauth2/v4 v4.3.0-SSB github.com/superseriousbusiness/oauth2/v4 v4.3.0-SSB
github.com/tdewolff/minify/v2 v2.9.21 github.com/tdewolff/minify/v2 v2.9.21
github.com/tidwall/buntdb v1.2.4 // indirect github.com/tidwall/buntdb v1.2.4 // indirect
github.com/uptrace/bun v0.4.3
github.com/uptrace/bun/dialect/pgdialect v0.4.3
github.com/urfave/cli/v2 v2.3.0 github.com/urfave/cli/v2 v2.3.0
github.com/vmihailenco/msgpack/v5 v5.3.4 // indirect
github.com/wagslane/go-password-validator v0.3.0 github.com/wagslane/go-password-validator v0.3.0
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914 golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect
golang.org/x/text v0.3.6 golang.org/x/text v0.3.6
google.golang.org/appengine v1.6.7 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v2 v2.4.0
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect

167
go.sum
View file

@ -33,6 +33,8 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/ReneKroon/ttlcache v1.7.0 h1:8BkjFfrzVFXyrqnMtezAaJ6AHPSsVV10m6w28N/Fgkk= github.com/ReneKroon/ttlcache v1.7.0 h1:8BkjFfrzVFXyrqnMtezAaJ6AHPSsVV10m6w28N/Fgkk=
github.com/ReneKroon/ttlcache v1.7.0/go.mod h1:8BGGzdumrIjWxdRx8zpK6L3oGMWvIXdvB2GD1cfvd+I= github.com/ReneKroon/ttlcache v1.7.0/go.mod h1:8BGGzdumrIjWxdRx8zpK6L3oGMWvIXdvB2GD1cfvd+I=
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
@ -54,11 +56,16 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
github.com/coreos/go-oidc/v3 v3.0.0 h1:/mAA0XMgYJw2Uqm7WKGCsKnjitE/+A0FFbOmiRJm7LQ= github.com/coreos/go-oidc/v3 v3.0.0 h1:/mAA0XMgYJw2Uqm7WKGCsKnjitE/+A0FFbOmiRJm7LQ=
github.com/coreos/go-oidc/v3 v3.0.0/go.mod h1:rEJ/idjfUyfkBit1eI1fvyr+64/g9dcKpAm8MJMesvo= github.com/coreos/go-oidc/v3 v3.0.0/go.mod h1:rEJ/idjfUyfkBit1eI1fvyr+64/g9dcKpAm8MJMesvo=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU= github.com/cpuguy83/go-md2man/v2 v2.0.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU=
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/dave/jennifer v1.3.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg= github.com/dave/jennifer v1.3.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@ -104,7 +111,6 @@ github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/gavv/httpexpect v2.0.0+incompatible h1:1X9kcRshkSKEjNJJxX9Y9mQ5BRfbxU5kORdjhlA1yX8= github.com/gavv/httpexpect v2.0.0+incompatible h1:1X9kcRshkSKEjNJJxX9Y9mQ5BRfbxU5kORdjhlA1yX8=
github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc= github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc=
@ -131,13 +137,8 @@ github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-pg/pg/extra/pgdebug v0.2.0 h1:t62UhMiV6KYAxSWojwIJiyX06TdepkzCeIzdeb00184= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-pg/pg/extra/pgdebug v0.2.0/go.mod h1:KmW//PLshMAQunfInLv9mFIbYXuGplOY9bc6qo3CaY0= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-pg/pg/v10 v10.6.2/go.mod h1:BfgPoQnD2wXNd986RYEHzikqv9iE875PrFaZ9vXvtNM=
github.com/go-pg/pg/v10 v10.10.3 h1:WobSfk5I+v7XwD1h9x2B7n4slDzjdBIonJ5PID95Aag=
github.com/go-pg/pg/v10 v10.10.3/go.mod h1:EmoJGYErc+stNN/1Jf+o4csXuprjxcRztBnn6cHe38E=
github.com/go-pg/zerochecker v0.2.0 h1:pp7f72c3DobMWOb2ErtZsnrPaSvHd2W4o9//8HtF4mU=
github.com/go-pg/zerochecker v0.2.0/go.mod h1:NJZ4wKL0NmTtz0GKCoJ8kym6Xn/EQzXRl2OnAe7MmDo=
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM= github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM=
@ -149,13 +150,15 @@ github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+
github.com/go-playground/validator/v10 v10.6.1/go.mod h1:xm76BBt941f7yWdGnI2DVPFFg1UK3YY04qifoXU3lOk= github.com/go-playground/validator/v10 v10.6.1/go.mod h1:xm76BBt941f7yWdGnI2DVPFFg1UK3YY04qifoXU3lOk=
github.com/go-playground/validator/v10 v10.7.0 h1:gLi5ajTBBheLNt0ctewgq7eolXoDALQd5/y90Hh9ZgM= github.com/go-playground/validator/v10 v10.7.0 h1:gLi5ajTBBheLNt0ctewgq7eolXoDALQd5/y90Hh9ZgM=
github.com/go-playground/validator/v10 v10.7.0/go.mod h1:xm76BBt941f7yWdGnI2DVPFFg1UK3YY04qifoXU3lOk= github.com/go-playground/validator/v10 v10.7.0/go.mod h1:xm76BBt941f7yWdGnI2DVPFFg1UK3YY04qifoXU3lOk=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-test/deep v1.0.1 h1:UQhStjbkDClarlmv0am7OXXO4/GaPdCGiUiMTvi28sg= github.com/go-test/deep v1.0.1 h1:UQhStjbkDClarlmv0am7OXXO4/GaPdCGiUiMTvi28sg=
github.com/go-test/deep v1.0.1/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/go-test/deep v1.0.1/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b h1:khEcpUM4yFcxg4/FHQWkvVRmgijNXRfzkIDHh23ggEo= github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b h1:khEcpUM4yFcxg4/FHQWkvVRmgijNXRfzkIDHh23ggEo=
github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM=
github.com/goccy/go-json v0.5.1 h1:R9UYTOUvo7eIY9aeDMZ4L6OVtHaSr1k2No9W6MKjXrA= github.com/goccy/go-json v0.5.1 h1:R9UYTOUvo7eIY9aeDMZ4L6OVtHaSr1k2No9W6MKjXrA=
github.com/goccy/go-json v0.5.1/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.5.1/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
github.com/golang/geo v0.0.0-20200319012246-673a6f80352d/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= github.com/golang/geo v0.0.0-20200319012246-673a6f80352d/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 h1:gtexQ/VGyN+VVFRXSFiguSNcXmS6rkKT+X7FdIrTtfo= github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 h1:gtexQ/VGyN+VVFRXSFiguSNcXmS6rkKT+X7FdIrTtfo=
@ -186,7 +189,6 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
@ -200,7 +202,6 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
@ -243,6 +244,53 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/imkira/go-interpol v1.1.0 h1:KIiKr0VSG2CUW1hl1jpiyuzuJeKUUpC8iM1AIE7N1Vk= github.com/imkira/go-interpol v1.1.0 h1:KIiKr0VSG2CUW1hl1jpiyuzuJeKUUpC8iM1AIE7N1Vk=
github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA=
github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0=
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
github.com/jackc/pgconn v1.10.0 h1:4EYhlDVEMsJ30nNj0mmgwIUXoq7e9sMJrVC2ED6QlCU=
github.com/jackc/pgconn v1.10.0/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc=
github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A=
github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgproto3/v2 v2.1.1 h1:7PQ/4gLoqnl87ZxL7xjO0DR5gYuviDCZxQJsUlFW1eI=
github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg=
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
github.com/jackc/pgtype v1.8.1 h1:9k0IXtdJXHJbyAWQgbWr1lU+MEhPXZz6RIXxfR5oxXs=
github.com/jackc/pgtype v1.8.1/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
github.com/jackc/pgx/v4 v4.13.0 h1:JCjhT5vmhMAf/YwBHLvrBn4OGdIQBiFG6ym8Zmdx570=
github.com/jackc/pgx/v4 v4.13.0/go.mod h1:9P4X524sErlaxj0XSGZk7s+LD0eOyu1ZDUrrpznYDF0=
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= 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/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
@ -260,18 +308,30 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
github.com/klauspost/compress v1.10.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.10.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.10.10 h1:a/y8CglcM7gLGYmlbP/stPE5sR3hbhFRUjCBfd/0B3I= github.com/klauspost/compress v1.10.10 h1:a/y8CglcM7gLGYmlbP/stPE5sR3hbhFRUjCBfd/0B3I=
github.com/klauspost/compress v1.10.10/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.10.10/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw= github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8=
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2/go.mod h1:0KeJpeMD6o+O4hW7qJOT7vyQPKrWmj26uf5wMc/IiIs= github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2/go.mod h1:0KeJpeMD6o+O4hW7qJOT7vyQPKrWmj26uf5wMc/IiIs=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA= github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA=
@ -289,24 +349,17 @@ github.com/moul/http2curl v1.0.0 h1:dRMWoAtb+ePxMlLkrCbAqh4TlPHXvoGUSQ323/9Zahs=
github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0= github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0=
github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
github.com/onsi/gomega v1.14.0 h1:ep6kpPVwmr/nTbklSx2nrLNSIO62DoYAhnPNIMhK8gI=
github.com/onsi/gomega v1.14.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
@ -315,13 +368,22 @@ github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b h1:aUNXCGgukb4gtY
github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b/go.mod h1:wTPjTepVu7uJBYgZ0SdWHQlIas582j6cn2jgk4DDdlg= github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b/go.mod h1:wTPjTepVu7uJBYgZ0SdWHQlIas582j6cn2jgk4DDdlg=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
@ -330,6 +392,8 @@ github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIK
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
@ -382,6 +446,10 @@ github.com/ugorji/go v1.2.6/go.mod h1:anCg0y61KIhDlPZmnH+so+RQbysYVyDko0IMgJv0Nn
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/ugorji/go/codec v1.2.6 h1:7kbGefxLoDBuYXOms4yD7223OpNMMPNPZxXk5TvFcyQ= github.com/ugorji/go/codec v1.2.6 h1:7kbGefxLoDBuYXOms4yD7223OpNMMPNPZxXk5TvFcyQ=
github.com/ugorji/go/codec v1.2.6/go.mod h1:V6TCNZ4PHqoHGFZuSG1W8nrCzzdgA2DozYxWFFpvxTw= github.com/ugorji/go/codec v1.2.6/go.mod h1:V6TCNZ4PHqoHGFZuSG1W8nrCzzdgA2DozYxWFFpvxTw=
github.com/uptrace/bun v0.4.3 h1:x6bjDqwjxwM/9Q1eauhkznuvTrz/rLiCK2p4tT63sAE=
github.com/uptrace/bun v0.4.3/go.mod h1:aL6D9vPw8DXaTQTwGrEPtUderBYXx7ShUmPfnxnqscw=
github.com/uptrace/bun/dialect/pgdialect v0.4.3 h1:lM2IUKpU99110chKkupw3oTfXiOKpB0hTJIe6frqQDo=
github.com/uptrace/bun/dialect/pgdialect v0.4.3/go.mod h1:BaNvWejl32oKUhwpFkw/eNcWldzIlVY4nfw/sNul0s8=
github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M= github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
@ -389,16 +457,8 @@ github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyC
github.com/valyala/fasthttp v1.14.0 h1:67bfuW9azCMwW/Jlq/C+VeihNpAuJMWkYPBig1gdi3A= github.com/valyala/fasthttp v1.14.0 h1:67bfuW9azCMwW/Jlq/C+VeihNpAuJMWkYPBig1gdi3A=
github.com/valyala/fasthttp v1.14.0/go.mod h1:ol1PCaL0dX20wC0htZ7sYCsvCYmrouYra0zHzaclZhE= github.com/valyala/fasthttp v1.14.0/go.mod h1:ol1PCaL0dX20wC0htZ7sYCsvCYmrouYra0zHzaclZhE=
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
github.com/vmihailenco/bufpool v0.1.11 h1:gOq2WmBrq0i2yW5QJ16ykccQ4wH9UyEsgLm6czKAd94=
github.com/vmihailenco/bufpool v0.1.11/go.mod h1:AFf/MOy3l2CFTKbxwt0mp2MwnqjNEs5H/UxrkA5jxTQ=
github.com/vmihailenco/msgpack/v4 v4.3.11/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4=
github.com/vmihailenco/msgpack/v5 v5.0.0-beta.1/go.mod h1:xlngVLeyQ/Qi05oQxhQ+oTuqa03RjMwMfk/7/TCs+QI=
github.com/vmihailenco/msgpack/v5 v5.3.1/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc=
github.com/vmihailenco/msgpack/v5 v5.3.4 h1:qMKAwOV+meBw2Y8k9cVwAy7qErtYCwBzZ2ellBfvnqc= github.com/vmihailenco/msgpack/v5 v5.3.4 h1:qMKAwOV+meBw2Y8k9cVwAy7qErtYCwBzZ2ellBfvnqc=
github.com/vmihailenco/msgpack/v5 v5.3.4/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= github.com/vmihailenco/msgpack/v5 v5.3.4/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc=
github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI=
github.com/vmihailenco/tagparser v0.1.2 h1:gnjoVuB/kljJ5wICEEOpx98oXMWPLj22G67Vbd1qPqc=
github.com/vmihailenco/tagparser v0.1.2/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI=
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
github.com/wagslane/go-password-validator v0.3.0 h1:vfxOPzGHkz5S146HDpavl0cw1DSVP061Ry2PX0/ON6I= github.com/wagslane/go-password-validator v0.3.0 h1:vfxOPzGHkz5S146HDpavl0cw1DSVP061Ry2PX0/ON6I=
@ -419,25 +479,36 @@ github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZ
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opentelemetry.io/otel v0.13.0/go.mod h1:dlSNewoRYikTkotEnxdmuBHgzT+k/idJSfDv/FxEnOY= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/goleak v0.10.0 h1:G3eWbSNIskeRqtsN/1uI5B+eP73y3JUuBsv9AZjehb4= go.uber.org/goleak v0.10.0 h1:G3eWbSNIskeRqtsN/1uI5B+eP73y3JUuBsv9AZjehb4=
go.uber.org/goleak v0.10.0/go.mod h1:VCZuO8V8mFPlL0F5J5GK1rtHV3DrFcQ1R8ryq7FK0aI= go.uber.org/goleak v0.10.0/go.mod h1:VCZuO8V8mFPlL0F5J5GK1rtHV3DrFcQ1R8ryq7FK0aI=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
golang.org/x/crypto v0.0.0-20180527072434-ab813273cd59/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180527072434-ab813273cd59/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20180910181607-0e37d006457b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@ -484,6 +555,7 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@ -501,12 +573,8 @@ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
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-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q= golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q=
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@ -524,14 +592,17 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180525142821-c11f84a56e43/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180525142821-c11f84a56e43/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -561,17 +632,15 @@ golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-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-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -579,6 +648,7 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@ -592,14 +662,18 @@ golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
@ -607,6 +681,7 @@ golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
@ -625,8 +700,9 @@ golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roY
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -713,16 +789,15 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w= gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w=
gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
@ -743,8 +818,6 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
mellium.im/sasl v0.2.1 h1:nspKSRg7/SyO0cRGY71OkfHab8tf9kCts6a6oTDut0w=
mellium.im/sasl v0.2.1/go.mod h1:ROaEDLQNuf9vjKqE1SrAfnsobm2YKXT1gnN1uDp1PjQ=
mvdan.cc/xurls/v2 v2.3.0 h1:59Olnbt67UKpxF1EwVBopJvkSUBmgtb468E4GVWIZ1I= mvdan.cc/xurls/v2 v2.3.0 h1:59Olnbt67UKpxF1EwVBopJvkSUBmgtb468E4GVWIZ1I=
mvdan.cc/xurls/v2 v2.3.0/go.mod h1:AjuTy7gEiUArFMjgBBDU4SMxlfUYsRokpJQgNWOt3e4= mvdan.cc/xurls/v2 v2.3.0/go.mod h1:AjuTy7gEiUArFMjgBBDU4SMxlfUYsRokpJQgNWOt3e4=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=

View file

@ -101,7 +101,7 @@ func (m *Module) AccountCreatePOSTHandler(c *gin.Context) {
form.IP = signUpIP form.IP = signUpIP
ti, err := m.processor.AccountCreate(authed, form) ti, err := m.processor.AccountCreate(c.Request.Context(), authed, form)
if err != nil { if err != nil {
l.Errorf("internal server error while creating new account: %s", err) l.Errorf("internal server error while creating new account: %s", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})

View file

@ -70,7 +70,7 @@ func (m *Module) AccountGETHandler(c *gin.Context) {
return return
} }
acctInfo, err := m.processor.AccountGet(authed, targetAcctID) acctInfo, err := m.processor.AccountGet(c.Request.Context(), authed, targetAcctID)
if err != nil { if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "not found"}) c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
return return

View file

@ -122,7 +122,7 @@ func (m *Module) AccountUpdateCredentialsPATCHHandler(c *gin.Context) {
return return
} }
acctSensitive, err := m.processor.AccountUpdate(authed, form) acctSensitive, err := m.processor.AccountUpdate(c.Request.Context(), authed, form)
if err != nil { if err != nil {
l.Debugf("could not update account: %s", err) l.Debugf("could not update account: %s", err)
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})

View file

@ -79,7 +79,7 @@ func (suite *AccountUpdateTestSuite) TestAccountUpdateCredentialsPATCHHandler()
recorder := httptest.NewRecorder() recorder := httptest.NewRecorder()
ctx, _ := gin.CreateTestContext(recorder) ctx, _ := gin.CreateTestContext(recorder)
ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"]) ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"])
ctx.Set(oauth.SessionAuthorizedToken, oauth.TokenToOauthToken(suite.testTokens["local_account_1"])) ctx.Set(oauth.SessionAuthorizedToken, oauth.DBTokenToToken(suite.testTokens["local_account_1"]))
ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"]) ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"])
ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"]) ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"])
ctx.Request = httptest.NewRequest(http.MethodPatch, fmt.Sprintf("http://localhost:8080/%s", account.UpdateCredentialsPath), bytes.NewReader(requestBody.Bytes())) // the endpoint we're hitting ctx.Request = httptest.NewRequest(http.MethodPatch, fmt.Sprintf("http://localhost:8080/%s", account.UpdateCredentialsPath), bytes.NewReader(requestBody.Bytes())) // the endpoint we're hitting

View file

@ -59,7 +59,7 @@ func (m *Module) AccountVerifyGETHandler(c *gin.Context) {
return return
} }
acctSensitive, err := m.processor.AccountGet(authed, authed.Account.ID) acctSensitive, err := m.processor.AccountGet(c.Request.Context(), authed, authed.Account.ID)
if err != nil { if err != nil {
l.Debugf("error getting account from processor: %s", err) l.Debugf("error getting account from processor: %s", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": "internal server error"}) c.JSON(http.StatusInternalServerError, gin.H{"error": "internal server error"})

View file

@ -72,7 +72,7 @@ func (m *Module) AccountBlockPOSTHandler(c *gin.Context) {
return return
} }
relationship, errWithCode := m.processor.AccountBlockCreate(authed, targetAcctID) relationship, errWithCode := m.processor.AccountBlockCreate(c.Request.Context(), authed, targetAcctID)
if errWithCode != nil { if errWithCode != nil {
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()}) c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
return return

View file

@ -99,7 +99,7 @@ func (m *Module) AccountFollowPOSTHandler(c *gin.Context) {
} }
form.ID = targetAcctID form.ID = targetAcctID
relationship, errWithCode := m.processor.AccountFollowCreate(authed, form) relationship, errWithCode := m.processor.AccountFollowCreate(c.Request.Context(), authed, form)
if errWithCode != nil { if errWithCode != nil {
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()}) c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
return return

View file

@ -74,7 +74,7 @@ func (m *Module) AccountFollowersGETHandler(c *gin.Context) {
return return
} }
followers, errWithCode := m.processor.AccountFollowersGet(authed, targetAcctID) followers, errWithCode := m.processor.AccountFollowersGet(c.Request.Context(), authed, targetAcctID)
if errWithCode != nil { if errWithCode != nil {
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()}) c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
return return

View file

@ -74,7 +74,7 @@ func (m *Module) AccountFollowingGETHandler(c *gin.Context) {
return return
} }
following, errWithCode := m.processor.AccountFollowingGet(authed, targetAcctID) following, errWithCode := m.processor.AccountFollowingGet(c.Request.Context(), authed, targetAcctID)
if errWithCode != nil { if errWithCode != nil {
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()}) c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
return return

View file

@ -71,7 +71,7 @@ func (m *Module) AccountRelationshipsGETHandler(c *gin.Context) {
relationships := []model.Relationship{} relationships := []model.Relationship{}
for _, targetAccountID := range targetAccountIDs { for _, targetAccountID := range targetAccountIDs {
r, errWithCode := m.processor.AccountRelationshipGet(authed, targetAccountID) r, errWithCode := m.processor.AccountRelationshipGet(c.Request.Context(), authed, targetAccountID)
if err != nil { if err != nil {
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()}) c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
return return

View file

@ -166,7 +166,7 @@ func (m *Module) AccountStatusesGETHandler(c *gin.Context) {
mediaOnly = i mediaOnly = i
} }
statuses, errWithCode := m.processor.AccountStatusesGet(authed, targetAcctID, limit, excludeReplies, maxID, pinnedOnly, mediaOnly) statuses, errWithCode := m.processor.AccountStatusesGet(c.Request.Context(), authed, targetAcctID, limit, excludeReplies, maxID, pinnedOnly, mediaOnly)
if errWithCode != nil { if errWithCode != nil {
l.Debugf("error from processor account statuses get: %s", errWithCode) l.Debugf("error from processor account statuses get: %s", errWithCode)
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()}) c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})

View file

@ -72,7 +72,7 @@ func (m *Module) AccountUnblockPOSTHandler(c *gin.Context) {
return return
} }
relationship, errWithCode := m.processor.AccountBlockRemove(authed, targetAcctID) relationship, errWithCode := m.processor.AccountBlockRemove(c.Request.Context(), authed, targetAcctID)
if errWithCode != nil { if errWithCode != nil {
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()}) c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
return return

View file

@ -75,7 +75,7 @@ func (m *Module) AccountUnfollowPOSTHandler(c *gin.Context) {
return return
} }
relationship, errWithCode := m.processor.AccountFollowRemove(authed, targetAcctID) relationship, errWithCode := m.processor.AccountFollowRemove(c.Request.Context(), authed, targetAcctID)
if errWithCode != nil { if errWithCode != nil {
l.Debug(errWithCode.Error()) l.Debug(errWithCode.Error())
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()}) c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})

View file

@ -141,7 +141,7 @@ func (m *Module) DomainBlocksPOSTHandler(c *gin.Context) {
if imp { if imp {
// we're importing multiple blocks // we're importing multiple blocks
domainBlocks, err := m.processor.AdminDomainBlocksImport(authed, form) domainBlocks, err := m.processor.AdminDomainBlocksImport(c.Request.Context(), authed, form)
if err != nil { if err != nil {
l.Debugf("error importing domain blocks: %s", err) l.Debugf("error importing domain blocks: %s", err)
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
@ -150,7 +150,7 @@ func (m *Module) DomainBlocksPOSTHandler(c *gin.Context) {
c.JSON(http.StatusOK, domainBlocks) c.JSON(http.StatusOK, domainBlocks)
} else { } else {
// we're just creating one block // we're just creating one block
domainBlock, err := m.processor.AdminDomainBlockCreate(authed, form) domainBlock, err := m.processor.AdminDomainBlockCreate(c.Request.Context(), authed, form)
if err != nil { if err != nil {
l.Debugf("error creating domain block: %s", err) l.Debugf("error creating domain block: %s", err)
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})

View file

@ -68,7 +68,7 @@ func (m *Module) DomainBlockDELETEHandler(c *gin.Context) {
return return
} }
domainBlock, errWithCode := m.processor.AdminDomainBlockDelete(authed, domainBlockID) domainBlock, errWithCode := m.processor.AdminDomainBlockDelete(c.Request.Context(), authed, domainBlockID)
if errWithCode != nil { if errWithCode != nil {
l.Debugf("error deleting domain block: %s", errWithCode.Error()) l.Debugf("error deleting domain block: %s", errWithCode.Error())
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()}) c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})

View file

@ -81,7 +81,7 @@ func (m *Module) DomainBlockGETHandler(c *gin.Context) {
export = i export = i
} }
domainBlock, err := m.processor.AdminDomainBlockGet(authed, domainBlockID, export) domainBlock, err := m.processor.AdminDomainBlockGet(c.Request.Context(), authed, domainBlockID, export)
if err != nil { if err != nil {
l.Debugf("error getting domain block: %s", err) l.Debugf("error getting domain block: %s", err)
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})

View file

@ -81,7 +81,7 @@ func (m *Module) DomainBlocksGETHandler(c *gin.Context) {
export = i export = i
} }
domainBlocks, err := m.processor.AdminDomainBlocksGet(authed, export) domainBlocks, err := m.processor.AdminDomainBlocksGet(c.Request.Context(), authed, export)
if err != nil { if err != nil {
l.Debugf("error getting domain blocks: %s", err) l.Debugf("error getting domain blocks: %s", err)
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})

View file

@ -111,7 +111,7 @@ func (m *Module) emojiCreatePOSTHandler(c *gin.Context) {
return return
} }
mastoEmoji, err := m.processor.AdminEmojiCreate(authed, form) mastoEmoji, err := m.processor.AdminEmojiCreate(c.Request.Context(), authed, form)
if err != nil { if err != nil {
l.Debugf("error creating emoji: %s", err) l.Debugf("error creating emoji: %s", err)
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})

View file

@ -101,7 +101,7 @@ func (m *Module) AppsPOSTHandler(c *gin.Context) {
return return
} }
mastoApp, err := m.processor.AppCreate(authed, form) mastoApp, err := m.processor.AppCreate(c.Request.Context(), authed, form)
if err != nil { if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return return

View file

@ -28,7 +28,7 @@
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
"github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/db/pg" "github.com/superseriousbusiness/gotosocial/internal/db/bundb"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/oauth" "github.com/superseriousbusiness/gotosocial/internal/oauth"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
@ -104,7 +104,7 @@ func (suite *AuthTestSuite) SetupTest() {
log := logrus.New() log := logrus.New()
log.SetLevel(logrus.TraceLevel) log.SetLevel(logrus.TraceLevel)
db, err := pg.NewPostgresService(context.Background(), suite.config, log) db, err := bundb.NewBunDBService(context.Background(), suite.config, log)
if err != nil { if err != nil {
logrus.Panicf("error creating database connection: %s", err) logrus.Panicf("error creating database connection: %s", err)
} }
@ -120,23 +120,23 @@ func (suite *AuthTestSuite) SetupTest() {
} }
for _, m := range models { for _, m := range models {
if err := suite.db.CreateTable(m); err != nil { if err := suite.db.CreateTable(context.Background(), m); err != nil {
logrus.Panicf("db connection error: %s", err) logrus.Panicf("db connection error: %s", err)
} }
} }
suite.oauthServer = oauth.New(suite.db, log) suite.oauthServer = oauth.New(suite.db, log)
if err := suite.db.Put(suite.testAccount); err != nil { if err := suite.db.Put(context.Background(), suite.testAccount); err != nil {
logrus.Panicf("could not insert test account into db: %s", err) logrus.Panicf("could not insert test account into db: %s", err)
} }
if err := suite.db.Put(suite.testUser); err != nil { if err := suite.db.Put(context.Background(), suite.testUser); err != nil {
logrus.Panicf("could not insert test user into db: %s", err) logrus.Panicf("could not insert test user into db: %s", err)
} }
if err := suite.db.Put(suite.testClient); err != nil { if err := suite.db.Put(context.Background(), suite.testClient); err != nil {
logrus.Panicf("could not insert test client into db: %s", err) logrus.Panicf("could not insert test client into db: %s", err)
} }
if err := suite.db.Put(suite.testApplication); err != nil { if err := suite.db.Put(context.Background(), suite.testApplication); err != nil {
logrus.Panicf("could not insert test application into db: %s", err) logrus.Panicf("could not insert test application into db: %s", err)
} }
@ -152,7 +152,7 @@ func (suite *AuthTestSuite) TearDownTest() {
&gtsmodel.Application{}, &gtsmodel.Application{},
} }
for _, m := range models { for _, m := range models {
if err := suite.db.DropTable(m); err != nil { if err := suite.db.DropTable(context.Background(), m); err != nil {
logrus.Panicf("error dropping table: %s", err) logrus.Panicf("error dropping table: %s", err)
} }
} }

View file

@ -70,30 +70,23 @@ func (m *Module) AuthorizeGETHandler(c *gin.Context) {
c.JSON(http.StatusInternalServerError, gin.H{"error": "no client_id found in session"}) c.JSON(http.StatusInternalServerError, gin.H{"error": "no client_id found in session"})
return return
} }
app := &gtsmodel.Application{ app := &gtsmodel.Application{}
ClientID: clientID, if err := m.db.GetWhere(c.Request.Context(), []db.Where{{Key: sessionClientID, Value: app.ClientID}}, app); err != nil {
}
if err := m.db.GetWhere([]db.Where{{Key: sessionClientID, Value: app.ClientID}}, app); err != nil {
m.clearSession(s) m.clearSession(s)
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("no application found for client id %s", clientID)}) c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("no application found for client id %s", clientID)})
return return
} }
// we can also use the userid of the user to fetch their username from the db to greet them nicely <3 // we can also use the userid of the user to fetch their username from the db to greet them nicely <3
user := &gtsmodel.User{ user := &gtsmodel.User{}
ID: userID, if err := m.db.GetByID(c.Request.Context(), user.ID, user); err != nil {
}
if err := m.db.GetByID(user.ID, user); err != nil {
m.clearSession(s) m.clearSession(s)
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return return
} }
acct := &gtsmodel.Account{ acct, err := m.db.GetAccountByID(c.Request.Context(), user.AccountID)
ID: user.AccountID, if err != nil {
}
if err := m.db.GetByID(acct.ID, acct); err != nil {
m.clearSession(s) m.clearSession(s)
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return return

View file

@ -19,6 +19,7 @@
package auth package auth
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"net" "net"
@ -80,13 +81,13 @@ func (m *Module) CallbackGETHandler(c *gin.Context) {
app := &gtsmodel.Application{ app := &gtsmodel.Application{
ClientID: clientID, ClientID: clientID,
} }
if err := m.db.GetWhere([]db.Where{{Key: sessionClientID, Value: app.ClientID}}, app); err != nil { if err := m.db.GetWhere(c.Request.Context(), []db.Where{{Key: sessionClientID, Value: app.ClientID}}, app); err != nil {
m.clearSession(s) m.clearSession(s)
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("no application found for client id %s", clientID)}) c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("no application found for client id %s", clientID)})
return return
} }
user, err := m.parseUserFromClaims(claims, net.IP(c.ClientIP()), app.ID) user, err := m.parseUserFromClaims(c.Request.Context(), claims, net.IP(c.ClientIP()), app.ID)
if err != nil { if err != nil {
m.clearSession(s) m.clearSession(s)
c.JSON(http.StatusForbidden, gin.H{"error": err.Error()}) c.JSON(http.StatusForbidden, gin.H{"error": err.Error()})
@ -103,14 +104,14 @@ func (m *Module) CallbackGETHandler(c *gin.Context) {
c.Redirect(http.StatusFound, OauthAuthorizePath) c.Redirect(http.StatusFound, OauthAuthorizePath)
} }
func (m *Module) parseUserFromClaims(claims *oidc.Claims, ip net.IP, appID string) (*gtsmodel.User, error) { func (m *Module) parseUserFromClaims(ctx context.Context, claims *oidc.Claims, ip net.IP, appID string) (*gtsmodel.User, error) {
if claims.Email == "" { if claims.Email == "" {
return nil, errors.New("no email returned in claims") return nil, errors.New("no email returned in claims")
} }
// see if we already have a user for this email address // see if we already have a user for this email address
user := &gtsmodel.User{} user := &gtsmodel.User{}
err := m.db.GetWhere([]db.Where{{Key: "email", Value: claims.Email}}, user) err := m.db.GetWhere(ctx, []db.Where{{Key: "email", Value: claims.Email}}, user)
if err == nil { if err == nil {
// we do! so we can just return it // we do! so we can just return it
return user, nil return user, nil
@ -122,7 +123,7 @@ func (m *Module) parseUserFromClaims(claims *oidc.Claims, ip net.IP, appID strin
} }
// maybe we have an unconfirmed user // maybe we have an unconfirmed user
err = m.db.GetWhere([]db.Where{{Key: "unconfirmed_email", Value: claims.Email}}, user) err = m.db.GetWhere(ctx, []db.Where{{Key: "unconfirmed_email", Value: claims.Email}}, user)
if err == nil { if err == nil {
// user is unconfirmed so return an error // user is unconfirmed so return an error
return nil, fmt.Errorf("user with email address %s is unconfirmed", claims.Email) return nil, fmt.Errorf("user with email address %s is unconfirmed", claims.Email)
@ -137,9 +138,13 @@ func (m *Module) parseUserFromClaims(claims *oidc.Claims, ip net.IP, appID strin
// however, because we trust the OIDC provider, we should now create a user + account with the provided claims // however, because we trust the OIDC provider, we should now create a user + account with the provided claims
// check if the email address is available for use; if it's not there's nothing we can so // check if the email address is available for use; if it's not there's nothing we can so
if err := m.db.IsEmailAvailable(claims.Email); err != nil { emailAvailable, err := m.db.IsEmailAvailable(ctx, claims.Email)
if err != nil {
return nil, fmt.Errorf("email %s not available: %s", claims.Email, err) return nil, fmt.Errorf("email %s not available: %s", claims.Email, err)
} }
if !emailAvailable {
return nil, fmt.Errorf("email %s in use", claims.Email)
}
// now we need a username // now we need a username
var username string var username string
@ -180,12 +185,11 @@ func (m *Module) parseUserFromClaims(claims *oidc.Claims, ip net.IP, appID strin
// note that for the first iteration, iString is still "" when the check is made, so our first choice // note that for the first iteration, iString is still "" when the check is made, so our first choice
// is still the raw username with no integer stuck on the end // is still the raw username with no integer stuck on the end
for i := 1; !found; i = i + 1 { for i := 1; !found; i = i + 1 {
if err := m.db.IsUsernameAvailable(username + iString); err != nil { usernameAvailable, err := m.db.IsUsernameAvailable(ctx, username+iString)
if strings.Contains(err.Error(), "db error") { if err != nil {
// if there's an actual db error we should return return nil, err
return nil, fmt.Errorf("error checking username availability: %s", err) }
} if usernameAvailable {
} else {
// no error so we've found a username that works // no error so we've found a username that works
found = true found = true
username = username + iString username = username + iString
@ -209,7 +213,7 @@ func (m *Module) parseUserFromClaims(claims *oidc.Claims, ip net.IP, appID strin
password := uuid.NewString() + uuid.NewString() password := uuid.NewString() + uuid.NewString()
// create the user! this will also create an account and store it in the database so we don't need to do that here // create the user! this will also create an account and store it in the database so we don't need to do that here
user, err = m.db.NewSignup(username, "", m.config.AccountsConfig.RequireApproval, claims.Email, password, ip, "", appID, claims.EmailVerified, admin) user, err = m.db.NewSignup(ctx, username, "", m.config.AccountsConfig.RequireApproval, claims.Email, password, ip, "", appID, claims.EmailVerified, admin)
if err != nil { if err != nil {
return nil, fmt.Errorf("error creating user: %s", err) return nil, fmt.Errorf("error creating user: %s", err)
} }

View file

@ -49,15 +49,15 @@ func (m *Module) OauthTokenMiddleware(c *gin.Context) {
// fetch user's and account for this user id // fetch user's and account for this user id
user := &gtsmodel.User{} user := &gtsmodel.User{}
if err := m.db.GetByID(uid, user); err != nil || user == nil { if err := m.db.GetByID(c.Request.Context(), uid, user); err != nil || user == nil {
l.Warnf("no user found for validated uid %s", uid) l.Warnf("no user found for validated uid %s", uid)
return return
} }
c.Set(oauth.SessionAuthorizedUser, user) c.Set(oauth.SessionAuthorizedUser, user)
l.Tracef("set gin context %s to %+v", oauth.SessionAuthorizedUser, user) l.Tracef("set gin context %s to %+v", oauth.SessionAuthorizedUser, user)
acct := &gtsmodel.Account{} acct, err := m.db.GetAccountByID(c.Request.Context(), user.AccountID)
if err := m.db.GetByID(user.AccountID, acct); err != nil || acct == nil { if err != nil || acct == nil {
l.Warnf("no account found for validated user %s", uid) l.Warnf("no account found for validated user %s", uid)
return return
} }
@ -69,7 +69,7 @@ func (m *Module) OauthTokenMiddleware(c *gin.Context) {
if cid := ti.GetClientID(); cid != "" { if cid := ti.GetClientID(); cid != "" {
l.Tracef("authenticated client %s with bearer token, scope is %s", cid, ti.GetScope()) l.Tracef("authenticated client %s with bearer token, scope is %s", cid, ti.GetScope())
app := &gtsmodel.Application{} app := &gtsmodel.Application{}
if err := m.db.GetWhere([]db.Where{{Key: "client_id", Value: cid}}, app); err != nil { if err := m.db.GetWhere(c.Request.Context(), []db.Where{{Key: "client_id", Value: cid}}, app); err != nil {
l.Tracef("no app found for client %s", cid) l.Tracef("no app found for client %s", cid)
} }
c.Set(oauth.SessionAuthorizedApplication, app) c.Set(oauth.SessionAuthorizedApplication, app)

View file

@ -19,6 +19,7 @@
package auth package auth
import ( import (
"context"
"errors" "errors"
"net/http" "net/http"
@ -74,7 +75,7 @@ func (m *Module) SignInPOSTHandler(c *gin.Context) {
} }
l.Tracef("parsed form: %+v", form) l.Tracef("parsed form: %+v", form)
userid, err := m.ValidatePassword(form.Email, form.Password) userid, err := m.ValidatePassword(c.Request.Context(), form.Email, form.Password)
if err != nil { if err != nil {
c.String(http.StatusForbidden, err.Error()) c.String(http.StatusForbidden, err.Error())
m.clearSession(s) m.clearSession(s)
@ -96,7 +97,7 @@ func (m *Module) SignInPOSTHandler(c *gin.Context) {
// The goal is to authenticate the password against the one for that email // The goal is to authenticate the password against the one for that email
// address stored in the database. If OK, we return the userid (a ulid) for that user, // address stored in the database. If OK, we return the userid (a ulid) for that user,
// so that it can be used in further Oauth flows to generate a token/retreieve an oauth client from the db. // so that it can be used in further Oauth flows to generate a token/retreieve an oauth client from the db.
func (m *Module) ValidatePassword(email string, password string) (userid string, err error) { func (m *Module) ValidatePassword(ctx context.Context, email string, password string) (userid string, err error) {
l := m.log.WithField("func", "ValidatePassword") l := m.log.WithField("func", "ValidatePassword")
// make sure an email/password was provided and bail if not // make sure an email/password was provided and bail if not
@ -108,7 +109,7 @@ func (m *Module) ValidatePassword(email string, password string) (userid string,
// first we select the user from the database based on email address, bail if no user found for that email // first we select the user from the database based on email address, bail if no user found for that email
gtsUser := &gtsmodel.User{} gtsUser := &gtsmodel.User{}
if err := m.db.GetWhere([]db.Where{{Key: "email", Value: email}}, gtsUser); err != nil { if err := m.db.GetWhere(ctx, []db.Where{{Key: "email", Value: email}}, gtsUser); err != nil {
l.Debugf("user %s was not retrievable from db during oauth authorization attempt: %s", email, err) l.Debugf("user %s was not retrievable from db during oauth authorization attempt: %s", email, err)
return incorrectPassword() return incorrectPassword()
} }

View file

@ -117,7 +117,7 @@ func (m *Module) BlocksGETHandler(c *gin.Context) {
limit = int(i) limit = int(i)
} }
resp, errWithCode := m.processor.BlocksGet(authed, maxID, sinceID, limit) resp, errWithCode := m.processor.BlocksGet(c.Request.Context(), authed, maxID, sinceID, limit)
if errWithCode != nil { if errWithCode != nil {
l.Debugf("error from processor BlocksGet: %s", errWithCode) l.Debugf("error from processor BlocksGet: %s", errWithCode)
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()}) c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})

View file

@ -43,7 +43,7 @@ func (m *Module) FavouritesGETHandler(c *gin.Context) {
limit = int(i) limit = int(i)
} }
resp, errWithCode := m.processor.FavedTimelineGet(authed, maxID, minID, limit) resp, errWithCode := m.processor.FavedTimelineGet(c.Request.Context(), authed, maxID, minID, limit)
if errWithCode != nil { if errWithCode != nil {
l.Debugf("error from processor FavedTimelineGet: %s", errWithCode) l.Debugf("error from processor FavedTimelineGet: %s", errWithCode)
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()}) c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})

View file

@ -25,8 +25,6 @@
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/superseriousbusiness/gotosocial/internal/api" "github.com/superseriousbusiness/gotosocial/internal/api"
"github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/processing" "github.com/superseriousbusiness/gotosocial/internal/processing"
"github.com/superseriousbusiness/gotosocial/internal/router" "github.com/superseriousbusiness/gotosocial/internal/router"
) )
@ -66,17 +64,3 @@ func (m *FileServer) Route(s router.Router) error {
s.AttachHandler(http.MethodGet, fmt.Sprintf("%s/:%s/:%s/:%s/:%s", m.storageBase, AccountIDKey, MediaTypeKey, MediaSizeKey, FileNameKey), m.ServeFile) s.AttachHandler(http.MethodGet, fmt.Sprintf("%s/:%s/:%s/:%s/:%s", m.storageBase, AccountIDKey, MediaTypeKey, MediaSizeKey, FileNameKey), m.ServeFile)
return nil return nil
} }
// CreateTables populates necessary tables in the given DB
func (m *FileServer) CreateTables(db db.DB) error {
models := []interface{}{
&gtsmodel.MediaAttachment{},
}
for _, m := range models {
if err := db.CreateTable(m); err != nil {
return fmt.Errorf("error creating table: %s", err)
}
}
return nil
}

View file

@ -78,7 +78,7 @@ func (m *FileServer) ServeFile(c *gin.Context) {
return return
} }
content, err := m.processor.FileGet(authed, &model.GetContentRequestForm{ content, err := m.processor.FileGet(c.Request.Context(), authed, &model.GetContentRequestForm{
AccountID: accountID, AccountID: accountID,
MediaType: mediaType, MediaType: mediaType,
MediaSize: mediaSize, MediaSize: mediaSize,

View file

@ -48,7 +48,7 @@ func (m *Module) FollowRequestAcceptPOSTHandler(c *gin.Context) {
return return
} }
r, errWithCode := m.processor.FollowRequestAccept(authed, originAccountID) r, errWithCode := m.processor.FollowRequestAccept(c.Request.Context(), authed, originAccountID)
if errWithCode != nil { if errWithCode != nil {
l.Debug(errWithCode.Error()) l.Debug(errWithCode.Error())
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()}) c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})

View file

@ -41,7 +41,7 @@ func (m *Module) FollowRequestGETHandler(c *gin.Context) {
return return
} }
accts, errWithCode := m.processor.FollowRequestsGet(authed) accts, errWithCode := m.processor.FollowRequestsGet(c.Request.Context(), authed)
if errWithCode != nil { if errWithCode != nil {
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()}) c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
return return

View file

@ -31,7 +31,7 @@
func (m *Module) InstanceInformationGETHandler(c *gin.Context) { func (m *Module) InstanceInformationGETHandler(c *gin.Context) {
l := m.log.WithField("func", "InstanceInformationGETHandler") l := m.log.WithField("func", "InstanceInformationGETHandler")
instance, err := m.processor.InstanceGet(m.config.Host) instance, err := m.processor.InstanceGet(c.Request.Context(), m.config.Host)
if err != nil { if err != nil {
l.Debugf("error getting instance from processor: %s", err) l.Debugf("error getting instance from processor: %s", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": "internal server error"}) c.JSON(http.StatusInternalServerError, gin.H{"error": "internal server error"})

View file

@ -116,7 +116,7 @@ func (m *Module) InstanceUpdatePATCHHandler(c *gin.Context) {
return return
} }
i, errWithCode := m.processor.InstancePatch(form) i, errWithCode := m.processor.InstancePatch(c.Request.Context(), form)
if errWithCode != nil { if errWithCode != nil {
l.Debugf("error with instance patch request: %s", errWithCode.Error()) l.Debugf("error with instance patch request: %s", errWithCode.Error())
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()}) c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})

View file

@ -19,14 +19,11 @@
package media package media
import ( import (
"fmt"
"net/http" "net/http"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/superseriousbusiness/gotosocial/internal/api" "github.com/superseriousbusiness/gotosocial/internal/api"
"github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/processing" "github.com/superseriousbusiness/gotosocial/internal/processing"
"github.com/superseriousbusiness/gotosocial/internal/router" "github.com/superseriousbusiness/gotosocial/internal/router"
) )
@ -63,17 +60,3 @@ func (m *Module) Route(s router.Router) error {
s.AttachHandler(http.MethodPut, BasePathWithID, m.MediaPUTHandler) s.AttachHandler(http.MethodPut, BasePathWithID, m.MediaPUTHandler)
return nil return nil
} }
// CreateTables populates necessary tables in the given DB
func (m *Module) CreateTables(db db.DB) error {
models := []interface{}{
&gtsmodel.MediaAttachment{},
}
for _, m := range models {
if err := db.CreateTable(m); err != nil {
return fmt.Errorf("error creating table: %s", err)
}
}
return nil
}

View file

@ -108,7 +108,7 @@ func (m *Module) MediaCreatePOSTHandler(c *gin.Context) {
} }
l.Debug("calling processor media create func") l.Debug("calling processor media create func")
mastoAttachment, err := m.processor.MediaCreate(authed, form) mastoAttachment, err := m.processor.MediaCreate(c.Request.Context(), authed, form)
if err != nil { if err != nil {
l.Debugf("error creating attachment: %s", err) l.Debugf("error creating attachment: %s", err)
c.JSON(http.StatusUnprocessableEntity, gin.H{"error": err.Error()}) c.JSON(http.StatusUnprocessableEntity, gin.H{"error": err.Error()})

View file

@ -121,7 +121,7 @@ func (suite *MediaCreateTestSuite) TestStatusCreatePOSTImageHandlerSuccessful()
// set up the context for the request // set up the context for the request
t := suite.testTokens["local_account_1"] t := suite.testTokens["local_account_1"]
oauthToken := oauth.TokenToOauthToken(t) oauthToken := oauth.DBTokenToToken(t)
recorder := httptest.NewRecorder() recorder := httptest.NewRecorder()
ctx, _ := gin.CreateTestContext(recorder) ctx, _ := gin.CreateTestContext(recorder)
ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"]) ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"])

View file

@ -75,7 +75,7 @@ func (m *Module) MediaGETHandler(c *gin.Context) {
return return
} }
attachment, errWithCode := m.processor.MediaGet(authed, attachmentID) attachment, errWithCode := m.processor.MediaGet(c.Request.Context(), authed, attachmentID)
if errWithCode != nil { if errWithCode != nil {
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()}) c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
return return

View file

@ -122,7 +122,7 @@ func (m *Module) MediaPUTHandler(c *gin.Context) {
return return
} }
attachment, errWithCode := m.processor.MediaUpdate(authed, attachmentID, &form) attachment, errWithCode := m.processor.MediaUpdate(c.Request.Context(), authed, attachmentID, &form)
if errWithCode != nil { if errWithCode != nil {
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()}) c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
return return

View file

@ -68,7 +68,7 @@ func (m *Module) NotificationsGETHandler(c *gin.Context) {
sinceID = sinceIDString sinceID = sinceIDString
} }
notifs, errWithCode := m.processor.NotificationsGet(authed, limit, maxID, sinceID) notifs, errWithCode := m.processor.NotificationsGet(c.Request.Context(), authed, limit, maxID, sinceID)
if errWithCode != nil { if errWithCode != nil {
l.Debugf("error processing notifications get: %s", errWithCode.Error()) l.Debugf("error processing notifications get: %s", errWithCode.Error())
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()}) c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})

View file

@ -164,7 +164,7 @@ func (m *Module) SearchGETHandler(c *gin.Context) {
Following: following, Following: following,
} }
results, errWithCode := m.processor.SearchGet(authed, searchQuery) results, errWithCode := m.processor.SearchGet(c.Request.Context(), authed, searchQuery)
if errWithCode != nil { if errWithCode != nil {
l.Debugf("error searching: %s", errWithCode.Error()) l.Debugf("error searching: %s", errWithCode.Error())
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()}) c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})

View file

@ -87,7 +87,7 @@ func (m *Module) StatusBoostPOSTHandler(c *gin.Context) {
return return
} }
mastoStatus, errWithCode := m.processor.StatusBoost(authed, targetStatusID) mastoStatus, errWithCode := m.processor.StatusBoost(c.Request.Context(), authed, targetStatusID)
if errWithCode != nil { if errWithCode != nil {
l.Debugf("error processing status boost: %s", errWithCode.Error()) l.Debugf("error processing status boost: %s", errWithCode.Error())
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()}) c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})

View file

@ -67,7 +67,7 @@ func (suite *StatusBoostTestSuite) TearDownTest() {
func (suite *StatusBoostTestSuite) TestPostBoost() { func (suite *StatusBoostTestSuite) TestPostBoost() {
t := suite.testTokens["local_account_1"] t := suite.testTokens["local_account_1"]
oauthToken := oauth.TokenToOauthToken(t) oauthToken := oauth.DBTokenToToken(t)
targetStatus := suite.testStatuses["admin_account_status_1"] targetStatus := suite.testStatuses["admin_account_status_1"]
@ -133,7 +133,7 @@ func (suite *StatusBoostTestSuite) TestPostBoost() {
func (suite *StatusBoostTestSuite) TestPostUnboostable() { func (suite *StatusBoostTestSuite) TestPostUnboostable() {
t := suite.testTokens["local_account_1"] t := suite.testTokens["local_account_1"]
oauthToken := oauth.TokenToOauthToken(t) oauthToken := oauth.DBTokenToToken(t)
targetStatus := suite.testStatuses["local_account_2_status_4"] targetStatus := suite.testStatuses["local_account_2_status_4"]
@ -171,7 +171,7 @@ func (suite *StatusBoostTestSuite) TestPostUnboostable() {
func (suite *StatusBoostTestSuite) TestPostNotVisible() { func (suite *StatusBoostTestSuite) TestPostNotVisible() {
t := suite.testTokens["local_account_2"] t := suite.testTokens["local_account_2"]
oauthToken := oauth.TokenToOauthToken(t) oauthToken := oauth.DBTokenToToken(t)
targetStatus := suite.testStatuses["local_account_1_status_3"] // this is a mutual only status and these accounts aren't mutuals targetStatus := suite.testStatuses["local_account_1_status_3"] // this is a mutual only status and these accounts aren't mutuals

View file

@ -84,7 +84,7 @@ func (m *Module) StatusBoostedByGETHandler(c *gin.Context) {
return return
} }
mastoAccounts, err := m.processor.StatusBoostedBy(authed, targetStatusID) mastoAccounts, err := m.processor.StatusBoostedBy(c.Request.Context(), authed, targetStatusID)
if err != nil { if err != nil {
l.Debugf("error processing status boosted by request: %s", err) l.Debugf("error processing status boosted by request: %s", err)
c.JSON(http.StatusBadRequest, gin.H{"error": "bad request"}) c.JSON(http.StatusBadRequest, gin.H{"error": "bad request"})

View file

@ -86,7 +86,7 @@ func (m *Module) StatusContextGETHandler(c *gin.Context) {
return return
} }
statusContext, errWithCode := m.processor.StatusGetContext(authed, targetStatusID) statusContext, errWithCode := m.processor.StatusGetContext(c.Request.Context(), authed, targetStatusID)
if errWithCode != nil { if errWithCode != nil {
l.Debugf("error getting status context: %s", errWithCode.Error()) l.Debugf("error getting status context: %s", errWithCode.Error())
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()}) c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})

View file

@ -101,7 +101,7 @@ func (m *Module) StatusCreatePOSTHandler(c *gin.Context) {
return return
} }
mastoStatus, err := m.processor.StatusCreate(authed, form) mastoStatus, err := m.processor.StatusCreate(c.Request.Context(), authed, form)
if err != nil { if err != nil {
l.Debugf("error processing status create: %s", err) l.Debugf("error processing status create: %s", err)
c.JSON(http.StatusBadRequest, gin.H{"error": "bad request"}) c.JSON(http.StatusBadRequest, gin.H{"error": "bad request"})

View file

@ -19,6 +19,7 @@
package status_test package status_test
import ( import (
"context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
@ -82,7 +83,7 @@ func (suite *StatusCreateTestSuite) TearDownTest() {
func (suite *StatusCreateTestSuite) TestPostNewStatus() { func (suite *StatusCreateTestSuite) TestPostNewStatus() {
t := suite.testTokens["local_account_1"] t := suite.testTokens["local_account_1"]
oauthToken := oauth.TokenToOauthToken(t) oauthToken := oauth.DBTokenToToken(t)
// setup // setup
recorder := httptest.NewRecorder() recorder := httptest.NewRecorder()
@ -128,7 +129,7 @@ func (suite *StatusCreateTestSuite) TestPostNewStatus() {
}, statusReply.Tags[0]) }, statusReply.Tags[0])
gtsTag := &gtsmodel.Tag{} gtsTag := &gtsmodel.Tag{}
err = suite.db.GetWhere([]db.Where{{Key: "name", Value: "helloworld"}}, gtsTag) err = suite.db.GetWhere(context.Background(), []db.Where{{Key: "name", Value: "helloworld"}}, gtsTag)
assert.NoError(suite.T(), err) assert.NoError(suite.T(), err)
assert.Equal(suite.T(), statusReply.Account.ID, gtsTag.FirstSeenFromAccountID) assert.Equal(suite.T(), statusReply.Account.ID, gtsTag.FirstSeenFromAccountID)
} }
@ -136,7 +137,7 @@ func (suite *StatusCreateTestSuite) TestPostNewStatus() {
func (suite *StatusCreateTestSuite) TestPostAnotherNewStatus() { func (suite *StatusCreateTestSuite) TestPostAnotherNewStatus() {
t := suite.testTokens["local_account_1"] t := suite.testTokens["local_account_1"]
oauthToken := oauth.TokenToOauthToken(t) oauthToken := oauth.DBTokenToToken(t)
// setup // setup
recorder := httptest.NewRecorder() recorder := httptest.NewRecorder()
@ -171,7 +172,7 @@ func (suite *StatusCreateTestSuite) TestPostAnotherNewStatus() {
func (suite *StatusCreateTestSuite) TestPostNewStatusWithEmoji() { func (suite *StatusCreateTestSuite) TestPostNewStatusWithEmoji() {
t := suite.testTokens["local_account_1"] t := suite.testTokens["local_account_1"]
oauthToken := oauth.TokenToOauthToken(t) oauthToken := oauth.DBTokenToToken(t)
// setup // setup
recorder := httptest.NewRecorder() recorder := httptest.NewRecorder()
@ -212,7 +213,7 @@ func (suite *StatusCreateTestSuite) TestPostNewStatusWithEmoji() {
// Try to reply to a status that doesn't exist // Try to reply to a status that doesn't exist
func (suite *StatusCreateTestSuite) TestReplyToNonexistentStatus() { func (suite *StatusCreateTestSuite) TestReplyToNonexistentStatus() {
t := suite.testTokens["local_account_1"] t := suite.testTokens["local_account_1"]
oauthToken := oauth.TokenToOauthToken(t) oauthToken := oauth.DBTokenToToken(t)
// setup // setup
recorder := httptest.NewRecorder() recorder := httptest.NewRecorder()
@ -243,7 +244,7 @@ func (suite *StatusCreateTestSuite) TestReplyToNonexistentStatus() {
// Post a reply to the status of a local user that allows replies. // Post a reply to the status of a local user that allows replies.
func (suite *StatusCreateTestSuite) TestReplyToLocalStatus() { func (suite *StatusCreateTestSuite) TestReplyToLocalStatus() {
t := suite.testTokens["local_account_1"] t := suite.testTokens["local_account_1"]
oauthToken := oauth.TokenToOauthToken(t) oauthToken := oauth.DBTokenToToken(t)
// setup // setup
recorder := httptest.NewRecorder() recorder := httptest.NewRecorder()
@ -283,7 +284,7 @@ func (suite *StatusCreateTestSuite) TestReplyToLocalStatus() {
// Take a media file which is currently not associated with a status, and attach it to a new status. // Take a media file which is currently not associated with a status, and attach it to a new status.
func (suite *StatusCreateTestSuite) TestAttachNewMediaSuccess() { func (suite *StatusCreateTestSuite) TestAttachNewMediaSuccess() {
t := suite.testTokens["local_account_1"] t := suite.testTokens["local_account_1"]
oauthToken := oauth.TokenToOauthToken(t) oauthToken := oauth.DBTokenToToken(t)
attachment := suite.testAttachments["local_account_1_unattached_1"] attachment := suite.testAttachments["local_account_1_unattached_1"]
@ -322,12 +323,11 @@ func (suite *StatusCreateTestSuite) TestAttachNewMediaSuccess() {
assert.Len(suite.T(), statusResponse.MediaAttachments, 1) assert.Len(suite.T(), statusResponse.MediaAttachments, 1)
// get the updated media attachment from the database // get the updated media attachment from the database
gtsAttachment := &gtsmodel.MediaAttachment{} gtsAttachment, err := suite.db.GetAttachmentByID(context.Background(), statusResponse.MediaAttachments[0].ID)
err = suite.db.GetByID(statusResponse.MediaAttachments[0].ID, gtsAttachment)
assert.NoError(suite.T(), err) assert.NoError(suite.T(), err)
// convert it to a masto attachment // convert it to a masto attachment
gtsAttachmentAsMasto, err := suite.tc.AttachmentToMasto(gtsAttachment) gtsAttachmentAsMasto, err := suite.tc.AttachmentToMasto(context.Background(), gtsAttachment)
assert.NoError(suite.T(), err) assert.NoError(suite.T(), err)
// compare it with what we have now // compare it with what we have now

View file

@ -86,7 +86,7 @@ func (m *Module) StatusDELETEHandler(c *gin.Context) {
return return
} }
mastoStatus, err := m.processor.StatusDelete(authed, targetStatusID) mastoStatus, err := m.processor.StatusDelete(c.Request.Context(), authed, targetStatusID)
if err != nil { if err != nil {
l.Debugf("error processing status delete: %s", err) l.Debugf("error processing status delete: %s", err)
c.JSON(http.StatusBadRequest, gin.H{"error": "bad request"}) c.JSON(http.StatusBadRequest, gin.H{"error": "bad request"})

View file

@ -83,7 +83,7 @@ func (m *Module) StatusFavePOSTHandler(c *gin.Context) {
return return
} }
mastoStatus, err := m.processor.StatusFave(authed, targetStatusID) mastoStatus, err := m.processor.StatusFave(c.Request.Context(), authed, targetStatusID)
if err != nil { if err != nil {
l.Debugf("error processing status fave: %s", err) l.Debugf("error processing status fave: %s", err)
c.JSON(http.StatusBadRequest, gin.H{"error": "bad request"}) c.JSON(http.StatusBadRequest, gin.H{"error": "bad request"})

View file

@ -71,7 +71,7 @@ func (suite *StatusFaveTestSuite) TearDownTest() {
func (suite *StatusFaveTestSuite) TestPostFave() { func (suite *StatusFaveTestSuite) TestPostFave() {
t := suite.testTokens["local_account_1"] t := suite.testTokens["local_account_1"]
oauthToken := oauth.TokenToOauthToken(t) oauthToken := oauth.DBTokenToToken(t)
targetStatus := suite.testStatuses["admin_account_status_2"] targetStatus := suite.testStatuses["admin_account_status_2"]
@ -119,7 +119,7 @@ func (suite *StatusFaveTestSuite) TestPostFave() {
func (suite *StatusFaveTestSuite) TestPostUnfaveable() { func (suite *StatusFaveTestSuite) TestPostUnfaveable() {
t := suite.testTokens["local_account_1"] t := suite.testTokens["local_account_1"]
oauthToken := oauth.TokenToOauthToken(t) oauthToken := oauth.DBTokenToToken(t)
targetStatus := suite.testStatuses["local_account_2_status_3"] // this one is unlikeable and unreplyable targetStatus := suite.testStatuses["local_account_2_status_3"] // this one is unlikeable and unreplyable

View file

@ -84,7 +84,7 @@ func (m *Module) StatusFavedByGETHandler(c *gin.Context) {
return return
} }
mastoAccounts, err := m.processor.StatusFavedBy(authed, targetStatusID) mastoAccounts, err := m.processor.StatusFavedBy(c.Request.Context(), authed, targetStatusID)
if err != nil { if err != nil {
l.Debugf("error processing status faved by request: %s", err) l.Debugf("error processing status faved by request: %s", err)
c.JSON(http.StatusBadRequest, gin.H{"error": "bad request"}) c.JSON(http.StatusBadRequest, gin.H{"error": "bad request"})

View file

@ -69,7 +69,7 @@ func (suite *StatusFavedByTestSuite) TearDownTest() {
func (suite *StatusFavedByTestSuite) TestGetFavedBy() { func (suite *StatusFavedByTestSuite) TestGetFavedBy() {
t := suite.testTokens["local_account_2"] t := suite.testTokens["local_account_2"]
oauthToken := oauth.TokenToOauthToken(t) oauthToken := oauth.DBTokenToToken(t)
targetStatus := suite.testStatuses["admin_account_status_1"] // this status is faved by local_account_1 targetStatus := suite.testStatuses["admin_account_status_1"] // this status is faved by local_account_1

View file

@ -83,7 +83,7 @@ func (m *Module) StatusGETHandler(c *gin.Context) {
return return
} }
mastoStatus, err := m.processor.StatusGet(authed, targetStatusID) mastoStatus, err := m.processor.StatusGet(c.Request.Context(), authed, targetStatusID)
if err != nil { if err != nil {
l.Debugf("error processing status get: %s", err) l.Debugf("error processing status get: %s", err)
c.JSON(http.StatusBadRequest, gin.H{"error": "bad request"}) c.JSON(http.StatusBadRequest, gin.H{"error": "bad request"})

View file

@ -84,7 +84,7 @@ func (m *Module) StatusUnboostPOSTHandler(c *gin.Context) {
return return
} }
mastoStatus, errWithCode := m.processor.StatusUnboost(authed, targetStatusID) mastoStatus, errWithCode := m.processor.StatusUnboost(c.Request.Context(), authed, targetStatusID)
if errWithCode != nil { if errWithCode != nil {
l.Debugf("error processing status unboost: %s", errWithCode.Error()) l.Debugf("error processing status unboost: %s", errWithCode.Error())
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()}) c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})

View file

@ -83,7 +83,7 @@ func (m *Module) StatusUnfavePOSTHandler(c *gin.Context) {
return return
} }
mastoStatus, err := m.processor.StatusUnfave(authed, targetStatusID) mastoStatus, err := m.processor.StatusUnfave(c.Request.Context(), authed, targetStatusID)
if err != nil { if err != nil {
l.Debugf("error processing status unfave: %s", err) l.Debugf("error processing status unfave: %s", err)
c.JSON(http.StatusBadRequest, gin.H{"error": "bad request"}) c.JSON(http.StatusBadRequest, gin.H{"error": "bad request"})

View file

@ -71,7 +71,7 @@ func (suite *StatusUnfaveTestSuite) TearDownTest() {
func (suite *StatusUnfaveTestSuite) TestPostUnfave() { func (suite *StatusUnfaveTestSuite) TestPostUnfave() {
t := suite.testTokens["local_account_1"] t := suite.testTokens["local_account_1"]
oauthToken := oauth.TokenToOauthToken(t) oauthToken := oauth.DBTokenToToken(t)
// this is the status we wanna unfave: in the testrig it's already faved by this account // this is the status we wanna unfave: in the testrig it's already faved by this account
targetStatus := suite.testStatuses["admin_account_status_1"] targetStatus := suite.testStatuses["admin_account_status_1"]
@ -120,7 +120,7 @@ func (suite *StatusUnfaveTestSuite) TestPostUnfave() {
func (suite *StatusUnfaveTestSuite) TestPostAlreadyNotFaved() { func (suite *StatusUnfaveTestSuite) TestPostAlreadyNotFaved() {
t := suite.testTokens["local_account_1"] t := suite.testTokens["local_account_1"]
oauthToken := oauth.TokenToOauthToken(t) oauthToken := oauth.DBTokenToToken(t)
// this is the status we wanna unfave: in the testrig it's not faved by this account // this is the status we wanna unfave: in the testrig it's not faved by this account
targetStatus := suite.testStatuses["admin_account_status_2"] targetStatus := suite.testStatuses["admin_account_status_2"]

View file

@ -122,7 +122,7 @@ func (m *Module) StreamGETHandler(c *gin.Context) {
} }
// make sure a valid token has been provided and obtain the associated account // make sure a valid token has been provided and obtain the associated account
account, err := m.processor.AuthorizeStreamingRequest(accessToken) account, err := m.processor.AuthorizeStreamingRequest(c.Request.Context(), accessToken)
if err != nil { if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "could not authorize with given token"}) c.JSON(http.StatusUnauthorized, gin.H{"error": "could not authorize with given token"})
return return
@ -147,7 +147,7 @@ func (m *Module) StreamGETHandler(c *gin.Context) {
defer conn.Close() // whatever happens, when we leave this function we want to close the websocket connection defer conn.Close() // whatever happens, when we leave this function we want to close the websocket connection
// inform the processor that we have a new connection and want a stream for it // inform the processor that we have a new connection and want a stream for it
stream, errWithCode := m.processor.OpenStreamForAccount(account, streamType) stream, errWithCode := m.processor.OpenStreamForAccount(c.Request.Context(), account, streamType)
if errWithCode != nil { if errWithCode != nil {
c.JSON(errWithCode.Code(), errWithCode.Safe()) c.JSON(errWithCode.Code(), errWithCode.Safe())
return return

View file

@ -153,7 +153,7 @@ func (m *Module) HomeTimelineGETHandler(c *gin.Context) {
local = i local = i
} }
resp, errWithCode := m.processor.HomeTimelineGet(authed, maxID, sinceID, minID, limit, local) resp, errWithCode := m.processor.HomeTimelineGet(c.Request.Context(), authed, maxID, sinceID, minID, limit, local)
if errWithCode != nil { if errWithCode != nil {
l.Debugf("error from processor HomeTimelineGet: %s", errWithCode) l.Debugf("error from processor HomeTimelineGet: %s", errWithCode)
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()}) c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})

View file

@ -153,7 +153,7 @@ func (m *Module) PublicTimelineGETHandler(c *gin.Context) {
local = i local = i
} }
resp, errWithCode := m.processor.PublicTimelineGet(authed, maxID, sinceID, minID, limit, local) resp, errWithCode := m.processor.PublicTimelineGet(c.Request.Context(), authed, maxID, sinceID, minID, limit, local)
if errWithCode != nil { if errWithCode != nil {
l.Debugf("error from processor PublicTimelineGet: %s", errWithCode) l.Debugf("error from processor PublicTimelineGet: %s", errWithCode)
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()}) c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})

View file

@ -33,7 +33,7 @@ func (m *Module) NodeInfoGETHandler(c *gin.Context) {
"user-agent": c.Request.UserAgent(), "user-agent": c.Request.UserAgent(),
}) })
ni, err := m.processor.GetNodeInfo(c.Request) ni, err := m.processor.GetNodeInfo(c.Request.Context(), c.Request)
if err != nil { if err != nil {
l.Debugf("error with get node info request: %s", err) l.Debugf("error with get node info request: %s", err)
c.JSON(err.Code(), err.Safe()) c.JSON(err.Code(), err.Safe())

View file

@ -33,7 +33,7 @@ func (m *Module) NodeInfoWellKnownGETHandler(c *gin.Context) {
"user-agent": c.Request.UserAgent(), "user-agent": c.Request.UserAgent(),
}) })
niRel, err := m.processor.GetNodeInfoRel(c.Request) niRel, err := m.processor.GetNodeInfoRel(c.Request.Context(), c.Request)
if err != nil { if err != nil {
l.Debugf("error with get node info rel request: %s", err) l.Debugf("error with get node info rel request: %s", err)
c.JSON(err.Code(), err.Safe()) c.JSON(err.Code(), err.Safe())

View file

@ -105,7 +105,7 @@ func (suite *UserGetTestSuite) TestGetUser() {
// convert person to account // convert person to account
// since this account is already known, we should get a pretty full model of it from the conversion // since this account is already known, we should get a pretty full model of it from the conversion
a, err := suite.tc.ASRepresentationToAccount(person, false) a, err := suite.tc.ASRepresentationToAccount(context.Background(), person, false)
assert.NoError(suite.T(), err) assert.NoError(suite.T(), err)
assert.EqualValues(suite.T(), targetAccount.Username, a.Username) assert.EqualValues(suite.T(), targetAccount.Username, a.Username)
} }

View file

@ -31,7 +31,7 @@ func (m *Module) SignatureCheck(c *gin.Context) {
// we managed to parse the url! // we managed to parse the url!
// if the domain is blocked we want to bail as early as possible // if the domain is blocked we want to bail as early as possible
blocked, err := m.db.IsURIBlocked(requestingPublicKeyID) blocked, err := m.db.IsURIBlocked(c.Request.Context(), requestingPublicKeyID)
if err != nil { if err != nil {
l.Errorf("could not tell if domain %s was blocked or not: %s", requestingPublicKeyID.Host, err) l.Errorf("could not tell if domain %s was blocked or not: %s", requestingPublicKeyID.Host, err)
c.AbortWithStatus(http.StatusInternalServerError) c.AbortWithStatus(http.StatusInternalServerError)

View file

@ -37,7 +37,7 @@ type cache struct {
// New returns a new in-memory cache. // New returns a new in-memory cache.
func New() Cache { func New() Cache {
c := ttlcache.NewCache() c := ttlcache.NewCache()
c.SetTTL(30 * time.Second) c.SetTTL(5 * time.Minute)
cache := &cache{ cache := &cache{
c: c, c: c,
} }

View file

@ -28,7 +28,7 @@
"github.com/superseriousbusiness/gotosocial/internal/cliactions" "github.com/superseriousbusiness/gotosocial/internal/cliactions"
"github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/db/pg" "github.com/superseriousbusiness/gotosocial/internal/db/bundb"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/util" "github.com/superseriousbusiness/gotosocial/internal/util"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
@ -36,7 +36,7 @@
// Create creates a new account in the database using the provided flags. // Create creates a new account in the database using the provided flags.
var Create cliactions.GTSAction = func(ctx context.Context, c *config.Config, log *logrus.Logger) error { var Create cliactions.GTSAction = func(ctx context.Context, c *config.Config, log *logrus.Logger) error {
dbConn, err := pg.NewPostgresService(ctx, c, log) dbConn, err := bundb.NewBunDBService(ctx, c, log)
if err != nil { if err != nil {
return fmt.Errorf("error creating dbservice: %s", err) return fmt.Errorf("error creating dbservice: %s", err)
} }
@ -65,7 +65,7 @@
return err return err
} }
_, err = dbConn.NewSignup(username, "", false, email, password, nil, "", "", false, false) _, err = dbConn.NewSignup(ctx, username, "", false, email, password, nil, "", "", false, false)
if err != nil { if err != nil {
return err return err
} }
@ -75,7 +75,7 @@
// Confirm sets a user to Approved, sets Email to the current UnconfirmedEmail value, and sets ConfirmedAt to now. // Confirm sets a user to Approved, sets Email to the current UnconfirmedEmail value, and sets ConfirmedAt to now.
var Confirm cliactions.GTSAction = func(ctx context.Context, c *config.Config, log *logrus.Logger) error { var Confirm cliactions.GTSAction = func(ctx context.Context, c *config.Config, log *logrus.Logger) error {
dbConn, err := pg.NewPostgresService(ctx, c, log) dbConn, err := bundb.NewBunDBService(ctx, c, log)
if err != nil { if err != nil {
return fmt.Errorf("error creating dbservice: %s", err) return fmt.Errorf("error creating dbservice: %s", err)
} }
@ -88,20 +88,20 @@
return err return err
} }
a, err := dbConn.GetLocalAccountByUsername(username) a, err := dbConn.GetLocalAccountByUsername(ctx, username)
if err != nil { if err != nil {
return err return err
} }
u := &gtsmodel.User{} u := &gtsmodel.User{}
if err := dbConn.GetWhere([]db.Where{{Key: "account_id", Value: a.ID}}, u); err != nil { if err := dbConn.GetWhere(ctx, []db.Where{{Key: "account_id", Value: a.ID}}, u); err != nil {
return err return err
} }
u.Approved = true u.Approved = true
u.Email = u.UnconfirmedEmail u.Email = u.UnconfirmedEmail
u.ConfirmedAt = time.Now() u.ConfirmedAt = time.Now()
if err := dbConn.UpdateByID(u.ID, u); err != nil { if err := dbConn.UpdateByID(ctx, u.ID, u); err != nil {
return err return err
} }
@ -110,7 +110,7 @@
// Promote sets a user to admin. // Promote sets a user to admin.
var Promote cliactions.GTSAction = func(ctx context.Context, c *config.Config, log *logrus.Logger) error { var Promote cliactions.GTSAction = func(ctx context.Context, c *config.Config, log *logrus.Logger) error {
dbConn, err := pg.NewPostgresService(ctx, c, log) dbConn, err := bundb.NewBunDBService(ctx, c, log)
if err != nil { if err != nil {
return fmt.Errorf("error creating dbservice: %s", err) return fmt.Errorf("error creating dbservice: %s", err)
} }
@ -123,17 +123,17 @@
return err return err
} }
a, err := dbConn.GetLocalAccountByUsername(username) a, err := dbConn.GetLocalAccountByUsername(ctx, username)
if err != nil { if err != nil {
return err return err
} }
u := &gtsmodel.User{} u := &gtsmodel.User{}
if err := dbConn.GetWhere([]db.Where{{Key: "account_id", Value: a.ID}}, u); err != nil { if err := dbConn.GetWhere(ctx, []db.Where{{Key: "account_id", Value: a.ID}}, u); err != nil {
return err return err
} }
u.Admin = true u.Admin = true
if err := dbConn.UpdateByID(u.ID, u); err != nil { if err := dbConn.UpdateByID(ctx, u.ID, u); err != nil {
return err return err
} }
@ -142,7 +142,7 @@
// Demote sets admin on a user to false. // Demote sets admin on a user to false.
var Demote cliactions.GTSAction = func(ctx context.Context, c *config.Config, log *logrus.Logger) error { var Demote cliactions.GTSAction = func(ctx context.Context, c *config.Config, log *logrus.Logger) error {
dbConn, err := pg.NewPostgresService(ctx, c, log) dbConn, err := bundb.NewBunDBService(ctx, c, log)
if err != nil { if err != nil {
return fmt.Errorf("error creating dbservice: %s", err) return fmt.Errorf("error creating dbservice: %s", err)
} }
@ -155,17 +155,17 @@
return err return err
} }
a, err := dbConn.GetLocalAccountByUsername(username) a, err := dbConn.GetLocalAccountByUsername(ctx, username)
if err != nil { if err != nil {
return err return err
} }
u := &gtsmodel.User{} u := &gtsmodel.User{}
if err := dbConn.GetWhere([]db.Where{{Key: "account_id", Value: a.ID}}, u); err != nil { if err := dbConn.GetWhere(ctx, []db.Where{{Key: "account_id", Value: a.ID}}, u); err != nil {
return err return err
} }
u.Admin = false u.Admin = false
if err := dbConn.UpdateByID(u.ID, u); err != nil { if err := dbConn.UpdateByID(ctx, u.ID, u); err != nil {
return err return err
} }
@ -174,7 +174,7 @@
// Disable sets Disabled to true on a user. // Disable sets Disabled to true on a user.
var Disable cliactions.GTSAction = func(ctx context.Context, c *config.Config, log *logrus.Logger) error { var Disable cliactions.GTSAction = func(ctx context.Context, c *config.Config, log *logrus.Logger) error {
dbConn, err := pg.NewPostgresService(ctx, c, log) dbConn, err := bundb.NewBunDBService(ctx, c, log)
if err != nil { if err != nil {
return fmt.Errorf("error creating dbservice: %s", err) return fmt.Errorf("error creating dbservice: %s", err)
} }
@ -187,17 +187,17 @@
return err return err
} }
a, err := dbConn.GetLocalAccountByUsername(username) a, err := dbConn.GetLocalAccountByUsername(ctx, username)
if err != nil { if err != nil {
return err return err
} }
u := &gtsmodel.User{} u := &gtsmodel.User{}
if err := dbConn.GetWhere([]db.Where{{Key: "account_id", Value: a.ID}}, u); err != nil { if err := dbConn.GetWhere(ctx, []db.Where{{Key: "account_id", Value: a.ID}}, u); err != nil {
return err return err
} }
u.Disabled = true u.Disabled = true
if err := dbConn.UpdateByID(u.ID, u); err != nil { if err := dbConn.UpdateByID(ctx, u.ID, u); err != nil {
return err return err
} }
@ -212,7 +212,7 @@
// Password sets the password of target account. // Password sets the password of target account.
var Password cliactions.GTSAction = func(ctx context.Context, c *config.Config, log *logrus.Logger) error { var Password cliactions.GTSAction = func(ctx context.Context, c *config.Config, log *logrus.Logger) error {
dbConn, err := pg.NewPostgresService(ctx, c, log) dbConn, err := bundb.NewBunDBService(ctx, c, log)
if err != nil { if err != nil {
return fmt.Errorf("error creating dbservice: %s", err) return fmt.Errorf("error creating dbservice: %s", err)
} }
@ -233,13 +233,13 @@
return err return err
} }
a, err := dbConn.GetLocalAccountByUsername(username) a, err := dbConn.GetLocalAccountByUsername(ctx, username)
if err != nil { if err != nil {
return err return err
} }
u := &gtsmodel.User{} u := &gtsmodel.User{}
if err := dbConn.GetWhere([]db.Where{{Key: "account_id", Value: a.ID}}, u); err != nil { if err := dbConn.GetWhere(ctx, []db.Where{{Key: "account_id", Value: a.ID}}, u); err != nil {
return err return err
} }
@ -250,7 +250,7 @@
u.EncryptedPassword = string(pw) u.EncryptedPassword = string(pw)
if err := dbConn.UpdateByID(u.ID, u); err != nil { if err := dbConn.UpdateByID(ctx, u.ID, u); err != nil {
return err return err
} }

View file

@ -35,7 +35,7 @@
"github.com/superseriousbusiness/gotosocial/internal/blob" "github.com/superseriousbusiness/gotosocial/internal/blob"
"github.com/superseriousbusiness/gotosocial/internal/cliactions" "github.com/superseriousbusiness/gotosocial/internal/cliactions"
"github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db/pg" "github.com/superseriousbusiness/gotosocial/internal/db/bundb"
"github.com/superseriousbusiness/gotosocial/internal/federation" "github.com/superseriousbusiness/gotosocial/internal/federation"
"github.com/superseriousbusiness/gotosocial/internal/federation/federatingdb" "github.com/superseriousbusiness/gotosocial/internal/federation/federatingdb"
"github.com/superseriousbusiness/gotosocial/internal/gotosocial" "github.com/superseriousbusiness/gotosocial/internal/gotosocial"
@ -79,28 +79,28 @@
// Start creates and starts a gotosocial server // Start creates and starts a gotosocial server
var Start cliactions.GTSAction = func(ctx context.Context, c *config.Config, log *logrus.Logger) error { var Start cliactions.GTSAction = func(ctx context.Context, c *config.Config, log *logrus.Logger) error {
dbService, err := pg.NewPostgresService(ctx, c, log) dbService, err := bundb.NewBunDBService(ctx, c, log)
if err != nil { if err != nil {
return fmt.Errorf("error creating dbservice: %s", err) return fmt.Errorf("error creating dbservice: %s", err)
} }
for _, m := range models { for _, m := range models {
if err := dbService.CreateTable(m); err != nil { if err := dbService.CreateTable(ctx, m); err != nil {
return fmt.Errorf("table creation error: %s", err) return fmt.Errorf("table creation error: %s", err)
} }
} }
if err := dbService.CreateInstanceAccount(); err != nil { if err := dbService.CreateInstanceAccount(ctx); err != nil {
return fmt.Errorf("error creating instance account: %s", err) return fmt.Errorf("error creating instance account: %s", err)
} }
if err := dbService.CreateInstanceInstance(); err != nil { if err := dbService.CreateInstanceInstance(ctx); err != nil {
return fmt.Errorf("error creating instance instance: %s", err) return fmt.Errorf("error creating instance instance: %s", err)
} }
federatingDB := federatingdb.New(dbService, c, log) federatingDB := federatingdb.New(dbService, c, log)
router, err := router.New(c, dbService, log) router, err := router.New(ctx, c, dbService, log)
if err != nil { if err != nil {
return fmt.Errorf("error creating router: %s", err) return fmt.Errorf("error creating router: %s", err)
} }
@ -120,7 +120,7 @@
transportController := transport.NewController(c, dbService, &federation.Clock{}, http.DefaultClient, log) transportController := transport.NewController(c, dbService, &federation.Clock{}, http.DefaultClient, log)
federator := federation.NewFederator(dbService, federatingDB, transportController, c, log, typeConverter, mediaHandler) federator := federation.NewFederator(dbService, federatingDB, transportController, c, log, typeConverter, mediaHandler)
processor := processing.NewProcessor(c, typeConverter, federator, oauthServer, mediaHandler, storageBackend, timelineManager, dbService, log) processor := processing.NewProcessor(c, typeConverter, federator, oauthServer, mediaHandler, storageBackend, timelineManager, dbService, log)
if err := processor.Start(); err != nil { if err := processor.Start(ctx); err != nil {
return fmt.Errorf("error starting processor: %s", err) return fmt.Errorf("error starting processor: %s", err)
} }

View file

@ -63,7 +63,7 @@
federator := testrig.NewTestFederator(dbService, transportController, storageBackend) federator := testrig.NewTestFederator(dbService, transportController, storageBackend)
processor := testrig.NewTestProcessor(dbService, storageBackend, federator) processor := testrig.NewTestProcessor(dbService, storageBackend, federator)
if err := processor.Start(); err != nil { if err := processor.Start(ctx); err != nil {
return fmt.Errorf("error starting processor: %s", err) return fmt.Errorf("error starting processor: %s", err)
} }

View file

@ -19,6 +19,7 @@
package db package db
import ( import (
"context"
"time" "time"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
@ -27,40 +28,43 @@
// Account contains functions related to account getting/setting/creation. // Account contains functions related to account getting/setting/creation.
type Account interface { type Account interface {
// GetAccountByID returns one account with the given ID, or an error if something goes wrong. // GetAccountByID returns one account with the given ID, or an error if something goes wrong.
GetAccountByID(id string) (*gtsmodel.Account, Error) GetAccountByID(ctx context.Context, id string) (*gtsmodel.Account, Error)
// GetAccountByURI returns one account with the given URI, or an error if something goes wrong. // GetAccountByURI returns one account with the given URI, or an error if something goes wrong.
GetAccountByURI(uri string) (*gtsmodel.Account, Error) GetAccountByURI(ctx context.Context, uri string) (*gtsmodel.Account, Error)
// GetAccountByURL returns one account with the given URL, or an error if something goes wrong. // GetAccountByURL returns one account with the given URL, or an error if something goes wrong.
GetAccountByURL(uri string) (*gtsmodel.Account, Error) GetAccountByURL(ctx context.Context, uri string) (*gtsmodel.Account, Error)
// UpdateAccount updates one account by ID.
UpdateAccount(ctx context.Context, account *gtsmodel.Account) (*gtsmodel.Account, Error)
// GetLocalAccountByUsername returns an account on this instance by its username. // GetLocalAccountByUsername returns an account on this instance by its username.
GetLocalAccountByUsername(username string) (*gtsmodel.Account, Error) GetLocalAccountByUsername(ctx context.Context, username string) (*gtsmodel.Account, Error)
// GetAccountFaves fetches faves/likes created by the target accountID. // GetAccountFaves fetches faves/likes created by the target accountID.
GetAccountFaves(accountID string) ([]*gtsmodel.StatusFave, Error) GetAccountFaves(ctx context.Context, accountID string) ([]*gtsmodel.StatusFave, Error)
// GetAccountStatusesCount is a shortcut for the common action of counting statuses produced by accountID. // GetAccountStatusesCount is a shortcut for the common action of counting statuses produced by accountID.
CountAccountStatuses(accountID string) (int, Error) CountAccountStatuses(ctx context.Context, accountID string) (int, Error)
// GetAccountStatuses is a shortcut for getting the most recent statuses. accountID is optional, if not provided // GetAccountStatuses is a shortcut for getting the most recent statuses. accountID is optional, if not provided
// then all statuses will be returned. If limit is set to 0, the size of the returned slice will not be limited. This can // then all statuses will be returned. If limit is set to 0, the size of the returned slice will not be limited. This can
// be very memory intensive so you probably shouldn't do this! // be very memory intensive so you probably shouldn't do this!
// In case of no entries, a 'no entries' error will be returned // In case of no entries, a 'no entries' error will be returned
GetAccountStatuses(accountID string, limit int, excludeReplies bool, maxID string, pinnedOnly bool, mediaOnly bool) ([]*gtsmodel.Status, Error) GetAccountStatuses(ctx context.Context, accountID string, limit int, excludeReplies bool, maxID string, pinnedOnly bool, mediaOnly bool) ([]*gtsmodel.Status, Error)
GetAccountBlocks(accountID string, maxID string, sinceID string, limit int) ([]*gtsmodel.Account, string, string, Error) GetAccountBlocks(ctx context.Context, accountID string, maxID string, sinceID string, limit int) ([]*gtsmodel.Account, string, string, Error)
// GetAccountLastPosted simply gets the timestamp of the most recent post by the account. // GetAccountLastPosted simply gets the timestamp of the most recent post by the account.
// //
// The returned time will be zero if account has never posted anything. // The returned time will be zero if account has never posted anything.
GetAccountLastPosted(accountID string) (time.Time, Error) GetAccountLastPosted(ctx context.Context, accountID string) (time.Time, Error)
// SetAccountHeaderOrAvatar sets the header or avatar for the given accountID to the given media attachment. // SetAccountHeaderOrAvatar sets the header or avatar for the given accountID to the given media attachment.
SetAccountHeaderOrAvatar(mediaAttachment *gtsmodel.MediaAttachment, accountID string) Error SetAccountHeaderOrAvatar(ctx context.Context, mediaAttachment *gtsmodel.MediaAttachment, accountID string) Error
// GetInstanceAccount returns the instance account for the given domain. // GetInstanceAccount returns the instance account for the given domain.
// If domain is empty, this instance account will be returned. // If domain is empty, this instance account will be returned.
GetInstanceAccount(domain string) (*gtsmodel.Account, Error) GetInstanceAccount(ctx context.Context, domain string) (*gtsmodel.Account, Error)
} }

View file

@ -19,6 +19,7 @@
package db package db
import ( import (
"context"
"net" "net"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
@ -28,26 +29,26 @@
type Admin interface { type Admin interface {
// IsUsernameAvailable checks whether a given username is available on our domain. // IsUsernameAvailable checks whether a given username is available on our domain.
// Returns an error if the username is already taken, or something went wrong in the db. // Returns an error if the username is already taken, or something went wrong in the db.
IsUsernameAvailable(username string) Error IsUsernameAvailable(ctx context.Context, username string) (bool, Error)
// IsEmailAvailable checks whether a given email address for a new account is available to be used on our domain. // IsEmailAvailable checks whether a given email address for a new account is available to be used on our domain.
// Return an error if: // Return an error if:
// A) the email is already associated with an account // A) the email is already associated with an account
// B) we block signups from this email domain // B) we block signups from this email domain
// C) something went wrong in the db // C) something went wrong in the db
IsEmailAvailable(email string) Error IsEmailAvailable(ctx context.Context, email string) (bool, Error)
// NewSignup creates a new user in the database with the given parameters. // NewSignup creates a new user in the database with the given parameters.
// By the time this function is called, it should be assumed that all the parameters have passed validation! // By the time this function is called, it should be assumed that all the parameters have passed validation!
NewSignup(username string, reason string, requireApproval bool, email string, password string, signUpIP net.IP, locale string, appID string, emailVerified bool, admin bool) (*gtsmodel.User, Error) NewSignup(ctx context.Context, username string, reason string, requireApproval bool, email string, password string, signUpIP net.IP, locale string, appID string, emailVerified bool, admin bool) (*gtsmodel.User, Error)
// CreateInstanceAccount creates an account in the database with the same username as the instance host value. // CreateInstanceAccount creates an account in the database with the same username as the instance host value.
// Ie., if the instance is hosted at 'example.org' the instance user will have a username of 'example.org'. // Ie., if the instance is hosted at 'example.org' the instance user will have a username of 'example.org'.
// This is needed for things like serving files that belong to the instance and not an individual user/account. // This is needed for things like serving files that belong to the instance and not an individual user/account.
CreateInstanceAccount() Error CreateInstanceAccount(ctx context.Context) Error
// CreateInstanceInstance creates an instance in the database with the same domain as the instance host value. // CreateInstanceInstance creates an instance in the database with the same domain as the instance host value.
// Ie., if the instance is hosted at 'example.org' the instance will have a domain of 'example.org'. // Ie., if the instance is hosted at 'example.org' the instance will have a domain of 'example.org'.
// This is needed for things like serving instance information through /api/v1/instance // This is needed for things like serving instance information through /api/v1/instance
CreateInstanceInstance() Error CreateInstanceInstance(ctx context.Context) Error
} }

View file

@ -24,15 +24,11 @@
type Basic interface { type Basic interface {
// CreateTable creates a table for the given interface. // CreateTable creates a table for the given interface.
// For implementations that don't use tables, this can just return nil. // For implementations that don't use tables, this can just return nil.
CreateTable(i interface{}) Error CreateTable(ctx context.Context, i interface{}) Error
// DropTable drops the table for the given interface. // DropTable drops the table for the given interface.
// For implementations that don't use tables, this can just return nil. // For implementations that don't use tables, this can just return nil.
DropTable(i interface{}) Error DropTable(ctx context.Context, i interface{}) Error
// RegisterTable registers a table for use in many2many relations.
// For implementations that don't use tables, or many2many relations, this can just return nil.
RegisterTable(i interface{}) Error
// Stop should stop and close the database connection cleanly, returning an error if this is not possible. // Stop should stop and close the database connection cleanly, returning an error if this is not possible.
// If the database implementation doesn't need to be stopped, this can just return nil. // If the database implementation doesn't need to be stopped, this can just return nil.
@ -45,43 +41,38 @@ type Basic interface {
// for other implementations (for example, in-memory) it might just be the key of a map. // for other implementations (for example, in-memory) it might just be the key of a map.
// The given interface i will be set to the result of the query, whatever it is. Use a pointer or a slice. // The given interface i will be set to the result of the query, whatever it is. Use a pointer or a slice.
// In case of no entries, a 'no entries' error will be returned // In case of no entries, a 'no entries' error will be returned
GetByID(id string, i interface{}) Error GetByID(ctx context.Context, id string, i interface{}) Error
// GetWhere gets one entry where key = value. This is similar to GetByID but allows the caller to specify the // GetWhere gets one entry where key = value. This is similar to GetByID but allows the caller to specify the
// name of the key to select from. // name of the key to select from.
// The given interface i will be set to the result of the query, whatever it is. Use a pointer or a slice. // The given interface i will be set to the result of the query, whatever it is. Use a pointer or a slice.
// In case of no entries, a 'no entries' error will be returned // In case of no entries, a 'no entries' error will be returned
GetWhere(where []Where, i interface{}) Error GetWhere(ctx context.Context, where []Where, i interface{}) Error
// GetAll will try to get all entries of type i. // GetAll will try to get all entries of type i.
// The given interface i will be set to the result of the query, whatever it is. Use a pointer or a slice. // The given interface i will be set to the result of the query, whatever it is. Use a pointer or a slice.
// In case of no entries, a 'no entries' error will be returned // In case of no entries, a 'no entries' error will be returned
GetAll(i interface{}) Error GetAll(ctx context.Context, i interface{}) Error
// Put simply stores i. It is up to the implementation to figure out how to store it, and using what key. // Put simply stores i. It is up to the implementation to figure out how to store it, and using what key.
// The given interface i will be set to the result of the query, whatever it is. Use a pointer or a slice. // The given interface i will be set to the result of the query, whatever it is. Use a pointer or a slice.
Put(i interface{}) Error Put(ctx context.Context, i interface{}) Error
// Upsert stores or updates i based on the given conflict column, as in https://www.postgresqltutorial.com/postgresql-upsert/
// It is up to the implementation to figure out how to store it, and using what key.
// The given interface i will be set to the result of the query, whatever it is. Use a pointer or a slice.
Upsert(i interface{}, conflictColumn string) Error
// UpdateByID updates i with id id. // UpdateByID updates i with id id.
// The given interface i will be set to the result of the query, whatever it is. Use a pointer or a slice. // The given interface i will be set to the result of the query, whatever it is. Use a pointer or a slice.
UpdateByID(id string, i interface{}) Error UpdateByID(ctx context.Context, id string, i interface{}) Error
// UpdateOneByID updates interface i with database the given database id. It will update one field of key key and value value. // UpdateOneByID updates interface i with database the given database id. It will update one field of key key and value value.
UpdateOneByID(id string, key string, value interface{}, i interface{}) Error UpdateOneByID(ctx context.Context, id string, key string, value interface{}, i interface{}) Error
// UpdateWhere updates column key of interface i with the given value, where the given parameters apply. // UpdateWhere updates column key of interface i with the given value, where the given parameters apply.
UpdateWhere(where []Where, key string, value interface{}, i interface{}) Error UpdateWhere(ctx context.Context, where []Where, key string, value interface{}, i interface{}) Error
// DeleteByID removes i with id id. // DeleteByID removes i with id id.
// If i didn't exist anyway, then no error should be returned. // If i didn't exist anyway, then no error should be returned.
DeleteByID(id string, i interface{}) Error DeleteByID(ctx context.Context, id string, i interface{}) Error
// DeleteWhere deletes i where key = value // DeleteWhere deletes i where key = value
// If i didn't exist anyway, then no error should be returned. // If i didn't exist anyway, then no error should be returned.
DeleteWhere(where []Where, i interface{}) Error DeleteWhere(ctx context.Context, where []Where, i interface{}) Error
} }

View file

@ -16,70 +16,90 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package pg package bundb
import ( import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"strings"
"time" "time"
"github.com/go-pg/pg/v10"
"github.com/go-pg/pg/v10/orm"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/uptrace/bun"
) )
type accountDB struct { type accountDB struct {
config *config.Config config *config.Config
conn *pg.DB conn *bun.DB
log *logrus.Logger log *logrus.Logger
cancel context.CancelFunc
} }
func (a *accountDB) newAccountQ(account *gtsmodel.Account) *orm.Query { func (a *accountDB) newAccountQ(account *gtsmodel.Account) *bun.SelectQuery {
return a.conn.Model(account). return a.conn.
NewSelect().
Model(account).
Relation("AvatarMediaAttachment"). Relation("AvatarMediaAttachment").
Relation("HeaderMediaAttachment") Relation("HeaderMediaAttachment")
} }
func (a *accountDB) GetAccountByID(id string) (*gtsmodel.Account, db.Error) { func (a *accountDB) GetAccountByID(ctx context.Context, id string) (*gtsmodel.Account, db.Error) {
account := &gtsmodel.Account{} account := new(gtsmodel.Account)
q := a.newAccountQ(account). q := a.newAccountQ(account).
Where("account.id = ?", id) Where("account.id = ?", id)
err := processErrorResponse(q.Select()) err := processErrorResponse(q.Scan(ctx))
return account, err return account, err
} }
func (a *accountDB) GetAccountByURI(uri string) (*gtsmodel.Account, db.Error) { func (a *accountDB) GetAccountByURI(ctx context.Context, uri string) (*gtsmodel.Account, db.Error) {
account := &gtsmodel.Account{} account := new(gtsmodel.Account)
q := a.newAccountQ(account). q := a.newAccountQ(account).
Where("account.uri = ?", uri) Where("account.uri = ?", uri)
err := processErrorResponse(q.Select()) err := processErrorResponse(q.Scan(ctx))
return account, err return account, err
} }
func (a *accountDB) GetAccountByURL(uri string) (*gtsmodel.Account, db.Error) { func (a *accountDB) GetAccountByURL(ctx context.Context, uri string) (*gtsmodel.Account, db.Error) {
account := &gtsmodel.Account{} account := new(gtsmodel.Account)
q := a.newAccountQ(account). q := a.newAccountQ(account).
Where("account.url = ?", uri) Where("account.url = ?", uri)
err := processErrorResponse(q.Select()) err := processErrorResponse(q.Scan(ctx))
return account, err return account, err
} }
func (a *accountDB) GetInstanceAccount(domain string) (*gtsmodel.Account, db.Error) { func (a *accountDB) UpdateAccount(ctx context.Context, account *gtsmodel.Account) (*gtsmodel.Account, db.Error) {
account := &gtsmodel.Account{} if strings.TrimSpace(account.ID) == "" {
return nil, errors.New("account had no ID")
}
account.UpdatedAt = time.Now()
q := a.conn.
NewUpdate().
Model(account).
WherePK()
_, err := q.Exec(ctx)
err = processErrorResponse(err)
return account, err
}
func (a *accountDB) GetInstanceAccount(ctx context.Context, domain string) (*gtsmodel.Account, db.Error) {
account := new(gtsmodel.Account)
q := a.newAccountQ(account) q := a.newAccountQ(account)
@ -90,29 +110,31 @@ func (a *accountDB) GetInstanceAccount(domain string) (*gtsmodel.Account, db.Err
} else { } else {
q = q. q = q.
Where("account.username = ?", domain). Where("account.username = ?", domain).
Where("? IS NULL", pg.Ident("domain")) Where("? IS NULL", bun.Ident("domain"))
} }
err := processErrorResponse(q.Select()) err := processErrorResponse(q.Scan(ctx))
return account, err return account, err
} }
func (a *accountDB) GetAccountLastPosted(accountID string) (time.Time, db.Error) { func (a *accountDB) GetAccountLastPosted(ctx context.Context, accountID string) (time.Time, db.Error) {
status := &gtsmodel.Status{} status := new(gtsmodel.Status)
q := a.conn.Model(status). q := a.conn.
NewSelect().
Model(status).
Order("id DESC"). Order("id DESC").
Limit(1). Limit(1).
Where("account_id = ?", accountID). Where("account_id = ?", accountID).
Column("created_at") Column("created_at")
err := processErrorResponse(q.Select()) err := processErrorResponse(q.Scan(ctx))
return status.CreatedAt, err return status.CreatedAt, err
} }
func (a *accountDB) SetAccountHeaderOrAvatar(mediaAttachment *gtsmodel.MediaAttachment, accountID string) db.Error { func (a *accountDB) SetAccountHeaderOrAvatar(ctx context.Context, mediaAttachment *gtsmodel.MediaAttachment, accountID string) db.Error {
if mediaAttachment.Avatar && mediaAttachment.Header { if mediaAttachment.Avatar && mediaAttachment.Header {
return errors.New("one media attachment cannot be both header and avatar") return errors.New("one media attachment cannot be both header and avatar")
} }
@ -127,51 +149,66 @@ func (a *accountDB) SetAccountHeaderOrAvatar(mediaAttachment *gtsmodel.MediaAtta
} }
// TODO: there are probably more side effects here that need to be handled // TODO: there are probably more side effects here that need to be handled
if _, err := a.conn.Model(mediaAttachment).OnConflict("(id) DO UPDATE").Insert(); err != nil { if _, err := a.conn.
NewInsert().
Model(mediaAttachment).
Exec(ctx); err != nil {
return err return err
} }
if _, err := a.conn.Model(&gtsmodel.Account{}).Set(fmt.Sprintf("%s_media_attachment_id = ?", headerOrAVI), mediaAttachment.ID).Where("id = ?", accountID).Update(); err != nil { if _, err := a.conn.
NewUpdate().
Model(&gtsmodel.Account{}).
Set(fmt.Sprintf("%s_media_attachment_id = ?", headerOrAVI), mediaAttachment.ID).
Where("id = ?", accountID).
Exec(ctx); err != nil {
return err return err
} }
return nil return nil
} }
func (a *accountDB) GetLocalAccountByUsername(username string) (*gtsmodel.Account, db.Error) { func (a *accountDB) GetLocalAccountByUsername(ctx context.Context, username string) (*gtsmodel.Account, db.Error) {
account := &gtsmodel.Account{} account := new(gtsmodel.Account)
q := a.newAccountQ(account). q := a.newAccountQ(account).
Where("username = ?", username). Where("username = ?", username).
Where("? IS NULL", pg.Ident("domain")) Where("? IS NULL", bun.Ident("domain"))
err := processErrorResponse(q.Select()) err := processErrorResponse(q.Scan(ctx))
return account, err return account, err
} }
func (a *accountDB) GetAccountFaves(accountID string) ([]*gtsmodel.StatusFave, db.Error) { func (a *accountDB) GetAccountFaves(ctx context.Context, accountID string) ([]*gtsmodel.StatusFave, db.Error) {
faves := []*gtsmodel.StatusFave{} faves := new([]*gtsmodel.StatusFave)
if err := a.conn.Model(&faves). if err := a.conn.
NewSelect().
Model(faves).
Where("account_id = ?", accountID). Where("account_id = ?", accountID).
Select(); err != nil { Scan(ctx); err != nil {
if err == pg.ErrNoRows {
return faves, nil
}
return nil, err return nil, err
} }
return faves, nil return *faves, nil
} }
func (a *accountDB) CountAccountStatuses(accountID string) (int, db.Error) { func (a *accountDB) CountAccountStatuses(ctx context.Context, accountID string) (int, db.Error) {
return a.conn.Model(&gtsmodel.Status{}).Where("account_id = ?", accountID).Count() return a.conn.
NewSelect().
Model(&gtsmodel.Status{}).
Where("account_id = ?", accountID).
Count(ctx)
} }
func (a *accountDB) GetAccountStatuses(accountID string, limit int, excludeReplies bool, maxID string, pinnedOnly bool, mediaOnly bool) ([]*gtsmodel.Status, db.Error) { func (a *accountDB) GetAccountStatuses(ctx context.Context, accountID string, limit int, excludeReplies bool, maxID string, pinnedOnly bool, mediaOnly bool) ([]*gtsmodel.Status, db.Error) {
a.log.Debugf("getting statuses for account %s", accountID) a.log.Debugf("getting statuses for account %s", accountID)
statuses := []*gtsmodel.Status{} statuses := []*gtsmodel.Status{}
q := a.conn.Model(&statuses).Order("id DESC") q := a.conn.
NewSelect().
Model(&statuses).
Order("id DESC")
if accountID != "" { if accountID != "" {
q = q.Where("account_id = ?", accountID) q = q.Where("account_id = ?", accountID)
} }
@ -181,27 +218,26 @@ func (a *accountDB) GetAccountStatuses(accountID string, limit int, excludeRepli
} }
if excludeReplies { if excludeReplies {
q = q.Where("? IS NULL", pg.Ident("in_reply_to_id")) q = q.Where("? IS NULL", bun.Ident("in_reply_to_id"))
} }
if pinnedOnly { if pinnedOnly {
q = q.Where("pinned = ?", true) q = q.Where("pinned = ?", true)
} }
if mediaOnly {
q = q.WhereGroup(func(q *pg.Query) (*pg.Query, error) {
return q.Where("? IS NOT NULL", pg.Ident("attachments")).Where("attachments != '{}'"), nil
})
}
if maxID != "" { if maxID != "" {
q = q.Where("id < ?", maxID) q = q.Where("id < ?", maxID)
} }
if err := q.Select(); err != nil { if mediaOnly {
if err == pg.ErrNoRows { q = q.WhereGroup(" AND ", func(q *bun.SelectQuery) *bun.SelectQuery {
return nil, db.ErrNoEntries return q.
} WhereOr("? IS NOT NULL", bun.Ident("attachments")).
WhereOr("attachments != '{}'")
})
}
if err := q.Scan(ctx); err != nil {
return nil, err return nil, err
} }
@ -213,10 +249,12 @@ func (a *accountDB) GetAccountStatuses(accountID string, limit int, excludeRepli
return statuses, nil return statuses, nil
} }
func (a *accountDB) GetAccountBlocks(accountID string, maxID string, sinceID string, limit int) ([]*gtsmodel.Account, string, string, db.Error) { func (a *accountDB) GetAccountBlocks(ctx context.Context, accountID string, maxID string, sinceID string, limit int) ([]*gtsmodel.Account, string, string, db.Error) {
blocks := []*gtsmodel.Block{} blocks := []*gtsmodel.Block{}
fq := a.conn.Model(&blocks). fq := a.conn.
NewSelect().
Model(&blocks).
Where("block.account_id = ?", accountID). Where("block.account_id = ?", accountID).
Relation("TargetAccount"). Relation("TargetAccount").
Order("block.id DESC") Order("block.id DESC")
@ -233,11 +271,8 @@ func (a *accountDB) GetAccountBlocks(accountID string, maxID string, sinceID str
fq = fq.Limit(limit) fq = fq.Limit(limit)
} }
err := fq.Select() err := fq.Scan(ctx)
if err != nil { if err != nil {
if err == pg.ErrNoRows {
return nil, "", "", db.ErrNoEntries
}
return nil, "", "", err return nil, "", "", err
} }

View file

@ -16,17 +16,19 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package pg_test package bundb_test
import ( import (
"context"
"testing" "testing"
"time"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
"github.com/superseriousbusiness/gotosocial/testrig" "github.com/superseriousbusiness/gotosocial/testrig"
) )
type AccountTestSuite struct { type AccountTestSuite struct {
PGStandardTestSuite BunDBStandardTestSuite
} }
func (suite *AccountTestSuite) SetupSuite() { func (suite *AccountTestSuite) SetupSuite() {
@ -54,7 +56,7 @@ func (suite *AccountTestSuite) TearDownTest() {
} }
func (suite *AccountTestSuite) TestGetAccountByIDWithExtras() { func (suite *AccountTestSuite) TestGetAccountByIDWithExtras() {
account, err := suite.db.GetAccountByID(suite.testAccounts["local_account_1"].ID) account, err := suite.db.GetAccountByID(context.Background(), suite.testAccounts["local_account_1"].ID)
if err != nil { if err != nil {
suite.FailNow(err.Error()) suite.FailNow(err.Error())
} }
@ -65,6 +67,20 @@ func (suite *AccountTestSuite) TestGetAccountByIDWithExtras() {
suite.NotEmpty(account.HeaderMediaAttachment.URL) suite.NotEmpty(account.HeaderMediaAttachment.URL)
} }
func (suite *AccountTestSuite) TestUpdateAccount() {
testAccount := suite.testAccounts["local_account_1"]
testAccount.DisplayName = "new display name!"
_, err := suite.db.UpdateAccount(context.Background(), testAccount)
suite.NoError(err)
updated, err := suite.db.GetAccountByID(context.Background(), testAccount.ID)
suite.NoError(err)
suite.Equal("new display name!", updated.DisplayName)
suite.WithinDuration(time.Now(), updated.UpdatedAt, 5*time.Second)
}
func TestAccountTestSuite(t *testing.T) { func TestAccountTestSuite(t *testing.T) {
suite.Run(t, new(AccountTestSuite)) suite.Run(t, new(AccountTestSuite))
} }

View file

@ -16,76 +16,76 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package pg package bundb
import ( import (
"context" "context"
"crypto/rand" "crypto/rand"
"crypto/rsa" "crypto/rsa"
"database/sql"
"fmt" "fmt"
"net" "net"
"net/mail" "net/mail"
"strings" "strings"
"time" "time"
"github.com/go-pg/pg/v10"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/id" "github.com/superseriousbusiness/gotosocial/internal/id"
"github.com/superseriousbusiness/gotosocial/internal/util" "github.com/superseriousbusiness/gotosocial/internal/util"
"github.com/uptrace/bun"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
) )
type adminDB struct { type adminDB struct {
config *config.Config config *config.Config
conn *pg.DB conn *bun.DB
log *logrus.Logger log *logrus.Logger
cancel context.CancelFunc
} }
func (a *adminDB) IsUsernameAvailable(username string) db.Error { func (a *adminDB) IsUsernameAvailable(ctx context.Context, username string) (bool, db.Error) {
// if no error we fail because it means we found something q := a.conn.
// if error but it's not pg.ErrNoRows then we fail NewSelect().
// if err is pg.ErrNoRows we're good, we found nothing so continue Model(&gtsmodel.Account{}).
if err := a.conn.Model(&gtsmodel.Account{}).Where("username = ?", username).Where("domain = ?", nil).Select(); err == nil { Where("username = ?", username).
return fmt.Errorf("username %s already in use", username) Where("domain = ?", nil)
} else if err != pg.ErrNoRows {
return fmt.Errorf("db error: %s", err) return notExists(ctx, q)
}
return nil
} }
func (a *adminDB) IsEmailAvailable(email string) db.Error { func (a *adminDB) IsEmailAvailable(ctx context.Context, email string) (bool, db.Error) {
// parse the domain from the email // parse the domain from the email
m, err := mail.ParseAddress(email) m, err := mail.ParseAddress(email)
if err != nil { if err != nil {
return fmt.Errorf("error parsing email address %s: %s", email, err) return false, fmt.Errorf("error parsing email address %s: %s", email, err)
} }
domain := strings.Split(m.Address, "@")[1] // domain will always be the second part after @ domain := strings.Split(m.Address, "@")[1] // domain will always be the second part after @
// check if the email domain is blocked // check if the email domain is blocked
if err := a.conn.Model(&gtsmodel.EmailDomainBlock{}).Where("domain = ?", domain).Select(); err == nil { if err := a.conn.
NewSelect().
Model(&gtsmodel.EmailDomainBlock{}).
Where("domain = ?", domain).
Scan(ctx); err == nil {
// fail because we found something // fail because we found something
return fmt.Errorf("email domain %s is blocked", domain) return false, fmt.Errorf("email domain %s is blocked", domain)
} else if err != pg.ErrNoRows { } else if err != sql.ErrNoRows {
// fail because we got an unexpected error return false, processErrorResponse(err)
return fmt.Errorf("db error: %s", err)
} }
// check if this email is associated with a user already // check if this email is associated with a user already
if err := a.conn.Model(&gtsmodel.User{}).Where("email = ?", email).WhereOr("unconfirmed_email = ?", email).Select(); err == nil { q := a.conn.
// fail because we found something NewSelect().
return fmt.Errorf("email %s already in use", email) Model(&gtsmodel.User{}).
} else if err != pg.ErrNoRows { Where("email = ?", email).
// fail because we got an unexpected error WhereOr("unconfirmed_email = ?", email)
return fmt.Errorf("db error: %s", err)
} return notExists(ctx, q)
return nil
} }
func (a *adminDB) NewSignup(username string, reason string, requireApproval bool, email string, password string, signUpIP net.IP, locale string, appID string, emailVerified bool, admin bool) (*gtsmodel.User, db.Error) { func (a *adminDB) NewSignup(ctx context.Context, username string, reason string, requireApproval bool, email string, password string, signUpIP net.IP, locale string, appID string, emailVerified bool, admin bool) (*gtsmodel.User, db.Error) {
key, err := rsa.GenerateKey(rand.Reader, 2048) key, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil { if err != nil {
a.log.Errorf("error creating new rsa key: %s", err) a.log.Errorf("error creating new rsa key: %s", err)
@ -94,13 +94,12 @@ func (a *adminDB) NewSignup(username string, reason string, requireApproval bool
// if something went wrong while creating a user, we might already have an account, so check here first... // if something went wrong while creating a user, we might already have an account, so check here first...
acct := &gtsmodel.Account{} acct := &gtsmodel.Account{}
err = a.conn.Model(acct).Where("username = ?", username).Where("? IS NULL", pg.Ident("domain")).Select() err = a.conn.NewSelect().
Model(acct).
Where("username = ?", username).
Where("? IS NULL", bun.Ident("domain")).
Scan(ctx)
if err != nil { if err != nil {
// there's been an actual error
if err != pg.ErrNoRows {
return nil, fmt.Errorf("db error checking existence of account: %s", err)
}
// we just don't have an account yet create one // we just don't have an account yet create one
newAccountURIs := util.GenerateURIsForAccount(username, a.config.Protocol, a.config.Host) newAccountURIs := util.GenerateURIsForAccount(username, a.config.Protocol, a.config.Host)
newAccountID, err := id.NewRandomULID() newAccountID, err := id.NewRandomULID()
@ -125,7 +124,10 @@ func (a *adminDB) NewSignup(username string, reason string, requireApproval bool
FollowingURI: newAccountURIs.FollowingURI, FollowingURI: newAccountURIs.FollowingURI,
FeaturedCollectionURI: newAccountURIs.CollectionURI, FeaturedCollectionURI: newAccountURIs.CollectionURI,
} }
if _, err = a.conn.Model(acct).Insert(); err != nil { if _, err = a.conn.
NewInsert().
Model(acct).
Exec(ctx); err != nil {
return nil, err return nil, err
} }
} }
@ -161,15 +163,33 @@ func (a *adminDB) NewSignup(username string, reason string, requireApproval bool
u.Moderator = true u.Moderator = true
} }
if _, err = a.conn.Model(u).Insert(); err != nil { if _, err = a.conn.
NewInsert().
Model(u).
Exec(ctx); err != nil {
return nil, err return nil, err
} }
return u, nil return u, nil
} }
func (a *adminDB) CreateInstanceAccount() db.Error { func (a *adminDB) CreateInstanceAccount(ctx context.Context) db.Error {
username := a.config.Host username := a.config.Host
// check if instance account already exists
existsQ := a.conn.
NewSelect().
Model(&gtsmodel.Account{}).
Where("username = ?", username).
Where("? IS NULL", bun.Ident("domain"))
count, err := existsQ.Count(ctx)
if err != nil && count == 1 {
a.log.Infof("instance account %s already exists", username)
return nil
} else if err != sql.ErrNoRows {
return processErrorResponse(err)
}
key, err := rsa.GenerateKey(rand.Reader, 2048) key, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil { if err != nil {
a.log.Errorf("error creating new rsa key: %s", err) a.log.Errorf("error creating new rsa key: %s", err)
@ -198,19 +218,36 @@ func (a *adminDB) CreateInstanceAccount() db.Error {
FollowingURI: newAccountURIs.FollowingURI, FollowingURI: newAccountURIs.FollowingURI,
FeaturedCollectionURI: newAccountURIs.CollectionURI, FeaturedCollectionURI: newAccountURIs.CollectionURI,
} }
inserted, err := a.conn.Model(acct).Where("username = ?", username).SelectOrInsert()
if err != nil { insertQ := a.conn.
NewInsert().
Model(acct)
if _, err := insertQ.Exec(ctx); err != nil {
return err return err
} }
if inserted {
a.log.Infof("created instance account %s with id %s", username, acct.ID) a.log.Infof("instance account %s CREATED with id %s", username, acct.ID)
} else {
a.log.Infof("instance account %s already exists with id %s", username, acct.ID)
}
return nil return nil
} }
func (a *adminDB) CreateInstanceInstance() db.Error { func (a *adminDB) CreateInstanceInstance(ctx context.Context) db.Error {
domain := a.config.Host
// check if instance entry already exists
existsQ := a.conn.
NewSelect().
Model(&gtsmodel.Instance{}).
Where("domain = ?", domain)
count, err := existsQ.Count(ctx)
if err != nil && count == 1 {
a.log.Infof("instance instance %s already exists", domain)
return nil
} else if err != sql.ErrNoRows {
return processErrorResponse(err)
}
iID, err := id.NewRandomULID() iID, err := id.NewRandomULID()
if err != nil { if err != nil {
return err return err
@ -218,18 +255,18 @@ func (a *adminDB) CreateInstanceInstance() db.Error {
i := &gtsmodel.Instance{ i := &gtsmodel.Instance{
ID: iID, ID: iID,
Domain: a.config.Host, Domain: domain,
Title: a.config.Host, Title: domain,
URI: fmt.Sprintf("%s://%s", a.config.Protocol, a.config.Host), URI: fmt.Sprintf("%s://%s", a.config.Protocol, a.config.Host),
} }
inserted, err := a.conn.Model(i).Where("domain = ?", a.config.Host).SelectOrInsert()
if err != nil { insertQ := a.conn.
NewInsert().
Model(i)
if _, err := insertQ.Exec(ctx); err != nil {
return err return err
} }
if inserted { a.log.Infof("created instance instance %s with id %s", domain, i.ID)
a.log.Infof("created instance instance %s with id %s", a.config.Host, i.ID)
} else {
a.log.Infof("instance instance %s already exists with id %s", a.config.Host, i.ID)
}
return nil return nil
} }

179
internal/db/bundb/basic.go Normal file
View file

@ -0,0 +1,179 @@
/*
GoToSocial
Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package bundb
import (
"context"
"errors"
"strings"
"github.com/sirupsen/logrus"
"github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/uptrace/bun"
)
type basicDB struct {
config *config.Config
conn *bun.DB
log *logrus.Logger
}
func (b *basicDB) Put(ctx context.Context, i interface{}) db.Error {
_, err := b.conn.NewInsert().Model(i).Exec(ctx)
if err != nil && strings.Contains(err.Error(), "duplicate key value violates unique constraint") {
return db.ErrAlreadyExists
}
return err
}
func (b *basicDB) GetByID(ctx context.Context, id string, i interface{}) db.Error {
q := b.conn.
NewSelect().
Model(i).
Where("id = ?", id)
return processErrorResponse(q.Scan(ctx))
}
func (b *basicDB) GetWhere(ctx context.Context, where []db.Where, i interface{}) db.Error {
if len(where) == 0 {
return errors.New("no queries provided")
}
q := b.conn.NewSelect().Model(i)
for _, w := range where {
if w.Value == nil {
q = q.Where("? IS NULL", bun.Ident(w.Key))
} else {
if w.CaseInsensitive {
q = q.Where("LOWER(?) = LOWER(?)", bun.Safe(w.Key), w.Value)
} else {
q = q.Where("? = ?", bun.Safe(w.Key), w.Value)
}
}
}
return processErrorResponse(q.Scan(ctx))
}
func (b *basicDB) GetAll(ctx context.Context, i interface{}) db.Error {
q := b.conn.
NewSelect().
Model(i)
return processErrorResponse(q.Scan(ctx))
}
func (b *basicDB) DeleteByID(ctx context.Context, id string, i interface{}) db.Error {
q := b.conn.
NewDelete().
Model(i).
Where("id = ?", id)
_, err := q.Exec(ctx)
return processErrorResponse(err)
}
func (b *basicDB) DeleteWhere(ctx context.Context, where []db.Where, i interface{}) db.Error {
if len(where) == 0 {
return errors.New("no queries provided")
}
q := b.conn.
NewDelete().
Model(i)
for _, w := range where {
q = q.Where("? = ?", bun.Safe(w.Key), w.Value)
}
_, err := q.Exec(ctx)
return processErrorResponse(err)
}
func (b *basicDB) UpdateByID(ctx context.Context, id string, i interface{}) db.Error {
q := b.conn.
NewUpdate().
Model(i).
WherePK()
_, err := q.Exec(ctx)
return processErrorResponse(err)
}
func (b *basicDB) UpdateOneByID(ctx context.Context, id string, key string, value interface{}, i interface{}) db.Error {
q := b.conn.NewUpdate().
Model(i).
Set("? = ?", bun.Safe(key), value).
WherePK()
_, err := q.Exec(ctx)
return processErrorResponse(err)
}
func (b *basicDB) UpdateWhere(ctx context.Context, where []db.Where, key string, value interface{}, i interface{}) db.Error {
q := b.conn.NewUpdate().Model(i)
for _, w := range where {
if w.Value == nil {
q = q.Where("? IS NULL", bun.Ident(w.Key))
} else {
if w.CaseInsensitive {
q = q.Where("LOWER(?) = LOWER(?)", bun.Safe(w.Key), w.Value)
} else {
q = q.Where("? = ?", bun.Safe(w.Key), w.Value)
}
}
}
q = q.Set("? = ?", bun.Safe(key), value)
_, err := q.Exec(ctx)
return processErrorResponse(err)
}
func (b *basicDB) CreateTable(ctx context.Context, i interface{}) db.Error {
_, err := b.conn.NewCreateTable().Model(i).IfNotExists().Exec(ctx)
return err
}
func (b *basicDB) DropTable(ctx context.Context, i interface{}) db.Error {
_, err := b.conn.NewDropTable().Model(i).IfExists().Exec(ctx)
return processErrorResponse(err)
}
func (b *basicDB) IsHealthy(ctx context.Context) db.Error {
return b.conn.Ping()
}
func (b *basicDB) Stop(ctx context.Context) db.Error {
b.log.Info("closing db connection")
if err := b.conn.Close(); err != nil {
// only cancel if there's a problem closing the db
return err
}
return nil
}

View file

@ -0,0 +1,68 @@
/*
GoToSocial
Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package bundb_test
import (
"context"
"testing"
"github.com/stretchr/testify/suite"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/testrig"
)
type BasicTestSuite struct {
BunDBStandardTestSuite
}
func (suite *BasicTestSuite) SetupSuite() {
suite.testTokens = testrig.NewTestTokens()
suite.testClients = testrig.NewTestClients()
suite.testApplications = testrig.NewTestApplications()
suite.testUsers = testrig.NewTestUsers()
suite.testAccounts = testrig.NewTestAccounts()
suite.testAttachments = testrig.NewTestAttachments()
suite.testStatuses = testrig.NewTestStatuses()
suite.testTags = testrig.NewTestTags()
suite.testMentions = testrig.NewTestMentions()
}
func (suite *BasicTestSuite) SetupTest() {
suite.config = testrig.NewTestConfig()
suite.db = testrig.NewTestDB()
suite.log = testrig.NewTestLog()
testrig.StandardDBSetup(suite.db, suite.testAccounts)
}
func (suite *BasicTestSuite) TearDownTest() {
testrig.StandardDBTeardown(suite.db)
}
func (suite *BasicTestSuite) TestGetAccountByID() {
testAccount := suite.testAccounts["local_account_1"]
a := &gtsmodel.Account{}
err := suite.db.GetByID(context.Background(), testAccount.ID, a)
suite.NoError(err)
}
func TestBasicTestSuite(t *testing.T) {
suite.Run(t, new(BasicTestSuite))
}

View file

@ -16,12 +16,13 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package pg package bundb
import ( import (
"context" "context"
"crypto/tls" "crypto/tls"
"crypto/x509" "crypto/x509"
"database/sql"
"encoding/pem" "encoding/pem"
"errors" "errors"
"fmt" "fmt"
@ -29,14 +30,20 @@
"strings" "strings"
"time" "time"
"github.com/go-pg/pg/extra/pgdebug" "github.com/jackc/pgx/v4"
"github.com/go-pg/pg/v10" "github.com/jackc/pgx/v4/stdlib"
"github.com/go-pg/pg/v10/orm"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/id" "github.com/superseriousbusiness/gotosocial/internal/id"
"github.com/uptrace/bun"
"github.com/uptrace/bun/dialect/pgdialect"
)
const (
dbTypePostgres = "postgres"
dbTypeSqlite = "sqlite"
) )
var registerTables []interface{} = []interface{}{ var registerTables []interface{} = []interface{}{
@ -44,8 +51,8 @@
&gtsmodel.StatusToTag{}, &gtsmodel.StatusToTag{},
} }
// postgresService satisfies the DB interface // bunDBService satisfies the DB interface
type postgresService struct { type bunDBService struct {
db.Account db.Account
db.Admin db.Admin
db.Basic db.Basic
@ -55,130 +62,115 @@ type postgresService struct {
db.Mention db.Mention
db.Notification db.Notification
db.Relationship db.Relationship
db.Session
db.Status db.Status
db.Timeline db.Timeline
config *config.Config config *config.Config
conn *pg.DB conn *bun.DB
log *logrus.Logger log *logrus.Logger
cancel context.CancelFunc
} }
// NewPostgresService returns a postgresService derived from the provided config, which implements the go-fed DB interface. // NewBunDBService returns a bunDB derived from the provided config, which implements the go-fed DB interface.
// Under the hood, it uses https://github.com/go-pg/pg to create and maintain a database connection. // Under the hood, it uses https://github.com/uptrace/bun to create and maintain a database connection.
func NewPostgresService(ctx context.Context, c *config.Config, log *logrus.Logger) (db.DB, error) { func NewBunDBService(ctx context.Context, c *config.Config, log *logrus.Logger) (db.DB, error) {
for _, t := range registerTables { var sqldb *sql.DB
// https://pg.uptrace.dev/orm/many-to-many-relation/ var conn *bun.DB
orm.RegisterTable(t)
}
opts, err := derivePGOptions(c) // depending on the database type we're trying to create, we need to use a different driver...
if err != nil { switch strings.ToLower(c.DBConfig.Type) {
return nil, fmt.Errorf("could not create postgres service: %s", err) case dbTypePostgres:
} // POSTGRES
log.Debugf("using pg options: %+v", opts) opts, err := deriveBunDBPGOptions(c)
if err != nil {
// create a connection return nil, fmt.Errorf("could not create bundb postgres options: %s", err)
pgCtx, cancel := context.WithCancel(ctx) }
conn := pg.Connect(opts).WithContext(pgCtx) sqldb = stdlib.OpenDB(*opts)
conn = bun.NewDB(sqldb, pgdialect.New())
// this will break the logfmt format we normally log in, case dbTypeSqlite:
// since we can't choose where pg outputs to and it defaults to // SQLITE
// stdout. So use this option with care! // TODO: https://bun.uptrace.dev/guide/drivers.html#sqlite
if log.GetLevel() >= logrus.TraceLevel { default:
conn.AddQueryHook(pgdebug.DebugHook{ return nil, fmt.Errorf("database type %s not supported for bundb", strings.ToLower(c.DBConfig.Type))
// Print all queries.
Verbose: true,
})
} }
// actually *begin* the connection so that we can tell if the db is there and listening // actually *begin* the connection so that we can tell if the db is there and listening
if err := conn.Ping(ctx); err != nil { if err := conn.Ping(); err != nil {
cancel()
return nil, fmt.Errorf("db connection error: %s", err) return nil, fmt.Errorf("db connection error: %s", err)
} }
log.Info("connected to database")
// print out discovered postgres version for _, t := range registerTables {
var version string // https://bun.uptrace.dev/orm/many-to-many-relation/
if _, err = conn.QueryOneContext(ctx, pg.Scan(&version), "SELECT version()"); err != nil { conn.RegisterModel(t)
cancel()
return nil, fmt.Errorf("db connection error: %s", err)
} }
log.Infof("connected to postgres version: %s", version)
ps := &postgresService{ ps := &bunDBService{
Account: &accountDB{ Account: &accountDB{
config: c, config: c,
conn: conn, conn: conn,
log: log, log: log,
cancel: cancel,
}, },
Admin: &adminDB{ Admin: &adminDB{
config: c, config: c,
conn: conn, conn: conn,
log: log, log: log,
cancel: cancel,
}, },
Basic: &basicDB{ Basic: &basicDB{
config: c, config: c,
conn: conn, conn: conn,
log: log, log: log,
cancel: cancel,
}, },
Domain: &domainDB{ Domain: &domainDB{
config: c, config: c,
conn: conn, conn: conn,
log: log, log: log,
cancel: cancel,
}, },
Instance: &instanceDB{ Instance: &instanceDB{
config: c, config: c,
conn: conn, conn: conn,
log: log, log: log,
cancel: cancel,
}, },
Media: &mediaDB{ Media: &mediaDB{
config: c, config: c,
conn: conn, conn: conn,
log: log, log: log,
cancel: cancel,
}, },
Mention: &mentionDB{ Mention: &mentionDB{
config: c, config: c,
conn: conn, conn: conn,
log: log, log: log,
cancel: cancel,
}, },
Notification: &notificationDB{ Notification: &notificationDB{
config: c, config: c,
conn: conn, conn: conn,
log: log, log: log,
cancel: cancel,
}, },
Relationship: &relationshipDB{ Relationship: &relationshipDB{
config: c, config: c,
conn: conn, conn: conn,
log: log, log: log,
cancel: cancel, },
Session: &sessionDB{
config: c,
conn: conn,
log: log,
}, },
Status: &statusDB{ Status: &statusDB{
config: c, config: c,
conn: conn, conn: conn,
log: log, log: log,
cancel: cancel,
}, },
Timeline: &timelineDB{ Timeline: &timelineDB{
config: c, config: c,
conn: conn, conn: conn,
log: log, log: log,
cancel: cancel,
}, },
config: c, config: c,
conn: conn, conn: conn,
log: log, log: log,
cancel: cancel,
} }
// we can confidently return this useable postgres service now // we can confidently return this useable service now
return ps, nil return ps, nil
} }
@ -186,9 +178,9 @@ func NewPostgresService(ctx context.Context, c *config.Config, log *logrus.Logge
HANDY STUFF HANDY STUFF
*/ */
// derivePGOptions takes an application config and returns either a ready-to-use *pg.Options // deriveBunDBPGOptions takes an application config and returns either a ready-to-use set of options
// with sensible defaults, or an error if it's not satisfied by the provided config. // with sensible defaults, or an error if it's not satisfied by the provided config.
func derivePGOptions(c *config.Config) (*pg.Options, error) { func deriveBunDBPGOptions(c *config.Config) (*pgx.ConnConfig, error) {
if strings.ToUpper(c.DBConfig.Type) != db.DBTypePostgres { if strings.ToUpper(c.DBConfig.Type) != db.DBTypePostgres {
return nil, fmt.Errorf("expected db type of %s but got %s", db.DBTypePostgres, c.DBConfig.Type) return nil, fmt.Errorf("expected db type of %s but got %s", db.DBTypePostgres, c.DBConfig.Type)
} }
@ -266,18 +258,16 @@ func derivePGOptions(c *config.Config) (*pg.Options, error) {
tlsConfig.RootCAs = certPool tlsConfig.RootCAs = certPool
} }
// We can rely on the pg library we're using to set cfg, _ := pgx.ParseConfig("")
// sensible defaults for everything we don't set here. cfg.Host = c.DBConfig.Address
options := &pg.Options{ cfg.Port = uint16(c.DBConfig.Port)
Addr: fmt.Sprintf("%s:%d", c.DBConfig.Address, c.DBConfig.Port), cfg.User = c.DBConfig.User
User: c.DBConfig.User, cfg.Password = c.DBConfig.Password
Password: c.DBConfig.Password, cfg.TLSConfig = tlsConfig
Database: c.DBConfig.Database, cfg.Database = c.DBConfig.Database
ApplicationName: c.ApplicationName, cfg.PreferSimpleProtocol = true
TLSConfig: tlsConfig,
}
return options, nil return cfg, nil
} }
/* /*
@ -286,9 +276,9 @@ func derivePGOptions(c *config.Config) (*pg.Options, error) {
// TODO: move these to the type converter, it's bananas that they're here and not there // TODO: move these to the type converter, it's bananas that they're here and not there
func (ps *postgresService) MentionStringsToMentions(targetAccounts []string, originAccountID string, statusID string) ([]*gtsmodel.Mention, error) { func (ps *bunDBService) MentionStringsToMentions(ctx context.Context, targetAccounts []string, originAccountID string, statusID string) ([]*gtsmodel.Mention, error) {
ogAccount := &gtsmodel.Account{} ogAccount := &gtsmodel.Account{}
if err := ps.conn.Model(ogAccount).Where("id = ?", originAccountID).Select(); err != nil { if err := ps.conn.NewSelect().Model(ogAccount).Where("id = ?", originAccountID).Scan(ctx); err != nil {
return nil, err return nil, err
} }
@ -333,14 +323,14 @@ func (ps *postgresService) MentionStringsToMentions(targetAccounts []string, ori
// match username + account, case insensitive // match username + account, case insensitive
if local { if local {
// local user -- should have a null domain // local user -- should have a null domain
err = ps.conn.Model(mentionedAccount).Where("LOWER(?) = LOWER(?)", pg.Ident("username"), username).Where("? IS NULL", pg.Ident("domain")).Select() err = ps.conn.NewSelect().Model(mentionedAccount).Where("LOWER(?) = LOWER(?)", bun.Ident("username"), username).Where("? IS NULL", bun.Ident("domain")).Scan(ctx)
} else { } else {
// remote user -- should have domain defined // remote user -- should have domain defined
err = ps.conn.Model(mentionedAccount).Where("LOWER(?) = LOWER(?)", pg.Ident("username"), username).Where("LOWER(?) = LOWER(?)", pg.Ident("domain"), domain).Select() err = ps.conn.NewSelect().Model(mentionedAccount).Where("LOWER(?) = LOWER(?)", bun.Ident("username"), username).Where("LOWER(?) = LOWER(?)", bun.Ident("domain"), domain).Scan(ctx)
} }
if err != nil { if err != nil {
if err == pg.ErrNoRows { if err == sql.ErrNoRows {
// no result found for this username/domain so just don't include it as a mencho and carry on about our business // no result found for this username/domain so just don't include it as a mencho and carry on about our business
ps.log.Debugf("no account found with username '%s' and domain '%s', skipping it", username, domain) ps.log.Debugf("no account found with username '%s' and domain '%s', skipping it", username, domain)
continue continue
@ -364,14 +354,14 @@ func (ps *postgresService) MentionStringsToMentions(targetAccounts []string, ori
return menchies, nil return menchies, nil
} }
func (ps *postgresService) TagStringsToTags(tags []string, originAccountID string, statusID string) ([]*gtsmodel.Tag, error) { func (ps *bunDBService) TagStringsToTags(ctx context.Context, tags []string, originAccountID string, statusID string) ([]*gtsmodel.Tag, error) {
newTags := []*gtsmodel.Tag{} newTags := []*gtsmodel.Tag{}
for _, t := range tags { for _, t := range tags {
tag := &gtsmodel.Tag{} tag := &gtsmodel.Tag{}
// we can use selectorinsert here to create the new tag if it doesn't exist already // we can use selectorinsert here to create the new tag if it doesn't exist already
// inserted will be true if this is a new tag we just created // inserted will be true if this is a new tag we just created
if err := ps.conn.Model(tag).Where("LOWER(?) = LOWER(?)", pg.Ident("name"), t).Select(); err != nil { if err := ps.conn.NewSelect().Model(tag).Where("LOWER(?) = LOWER(?)", bun.Ident("name"), t).Scan(ctx); err != nil {
if err == pg.ErrNoRows { if err == sql.ErrNoRows {
// tag doesn't exist yet so populate it // tag doesn't exist yet so populate it
newID, err := id.NewRandomULID() newID, err := id.NewRandomULID()
if err != nil { if err != nil {
@ -400,13 +390,13 @@ func (ps *postgresService) TagStringsToTags(tags []string, originAccountID strin
return newTags, nil return newTags, nil
} }
func (ps *postgresService) EmojiStringsToEmojis(emojis []string, originAccountID string, statusID string) ([]*gtsmodel.Emoji, error) { func (ps *bunDBService) EmojiStringsToEmojis(ctx context.Context, emojis []string, originAccountID string, statusID string) ([]*gtsmodel.Emoji, error) {
newEmojis := []*gtsmodel.Emoji{} newEmojis := []*gtsmodel.Emoji{}
for _, e := range emojis { for _, e := range emojis {
emoji := &gtsmodel.Emoji{} emoji := &gtsmodel.Emoji{}
err := ps.conn.Model(emoji).Where("shortcode = ?", e).Where("visible_in_picker = true").Where("disabled = false").Select() err := ps.conn.NewSelect().Model(emoji).Where("shortcode = ?", e).Where("visible_in_picker = true").Where("disabled = false").Scan(ctx)
if err != nil { if err != nil {
if err == pg.ErrNoRows { if err == sql.ErrNoRows {
// no result found for this username/domain so just don't include it as an emoji and carry on about our business // no result found for this username/domain so just don't include it as an emoji and carry on about our business
ps.log.Debugf("no emoji found with shortcode %s, skipping it", e) ps.log.Debugf("no emoji found with shortcode %s, skipping it", e)
continue continue

View file

@ -16,7 +16,7 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package pg_test package bundb_test
import ( import (
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
@ -27,7 +27,7 @@
"github.com/superseriousbusiness/gotosocial/internal/oauth" "github.com/superseriousbusiness/gotosocial/internal/oauth"
) )
type PGStandardTestSuite struct { type BunDBStandardTestSuite struct {
// standard suite interfaces // standard suite interfaces
suite.Suite suite.Suite
config *config.Config config *config.Config

View file

@ -16,48 +16,46 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package pg package bundb
import ( import (
"context" "context"
"net/url" "net/url"
"github.com/go-pg/pg/v10"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/util" "github.com/superseriousbusiness/gotosocial/internal/util"
"github.com/uptrace/bun"
) )
type domainDB struct { type domainDB struct {
config *config.Config config *config.Config
conn *pg.DB conn *bun.DB
log *logrus.Logger log *logrus.Logger
cancel context.CancelFunc
} }
func (d *domainDB) IsDomainBlocked(domain string) (bool, db.Error) { func (d *domainDB) IsDomainBlocked(ctx context.Context, domain string) (bool, db.Error) {
if domain == "" { if domain == "" {
return false, nil return false, nil
} }
blocked, err := d.conn. q := d.conn.
NewSelect().
Model(&gtsmodel.DomainBlock{}). Model(&gtsmodel.DomainBlock{}).
Where("LOWER(domain) = LOWER(?)", domain). Where("LOWER(domain) = LOWER(?)", domain).
Exists() Limit(1)
err = processErrorResponse(err) return exists(ctx, q)
return blocked, err
} }
func (d *domainDB) AreDomainsBlocked(domains []string) (bool, db.Error) { func (d *domainDB) AreDomainsBlocked(ctx context.Context, domains []string) (bool, db.Error) {
// filter out any doubles // filter out any doubles
uniqueDomains := util.UniqueStrings(domains) uniqueDomains := util.UniqueStrings(domains)
for _, domain := range uniqueDomains { for _, domain := range uniqueDomains {
if blocked, err := d.IsDomainBlocked(domain); err != nil { if blocked, err := d.IsDomainBlocked(ctx, domain); err != nil {
return false, err return false, err
} else if blocked { } else if blocked {
return blocked, nil return blocked, nil
@ -68,16 +66,16 @@ func (d *domainDB) AreDomainsBlocked(domains []string) (bool, db.Error) {
return false, nil return false, nil
} }
func (d *domainDB) IsURIBlocked(uri *url.URL) (bool, db.Error) { func (d *domainDB) IsURIBlocked(ctx context.Context, uri *url.URL) (bool, db.Error) {
domain := uri.Hostname() domain := uri.Hostname()
return d.IsDomainBlocked(domain) return d.IsDomainBlocked(ctx, domain)
} }
func (d *domainDB) AreURIsBlocked(uris []*url.URL) (bool, db.Error) { func (d *domainDB) AreURIsBlocked(ctx context.Context, uris []*url.URL) (bool, db.Error) {
domains := []string{} domains := []string{}
for _, uri := range uris { for _, uri := range uris {
domains = append(domains, uri.Hostname()) domains = append(domains, uri.Hostname())
} }
return d.AreDomainsBlocked(domains) return d.AreDomainsBlocked(ctx, domains)
} }

View file

@ -16,43 +16,50 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package pg package bundb
import ( import (
"context" "context"
"github.com/go-pg/pg/v10"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/uptrace/bun"
) )
type instanceDB struct { type instanceDB struct {
config *config.Config config *config.Config
conn *pg.DB conn *bun.DB
log *logrus.Logger log *logrus.Logger
cancel context.CancelFunc
} }
func (i *instanceDB) CountInstanceUsers(domain string) (int, db.Error) { func (i *instanceDB) CountInstanceUsers(ctx context.Context, domain string) (int, db.Error) {
q := i.conn.Model(&[]*gtsmodel.Account{}) q := i.conn.
NewSelect().
Model(&[]*gtsmodel.Account{})
if domain == i.config.Host { if domain == i.config.Host {
// if the domain is *this* domain, just count where the domain field is null // if the domain is *this* domain, just count where the domain field is null
q = q.Where("? IS NULL", pg.Ident("domain")) q = q.Where("? IS NULL", bun.Ident("domain"))
} else { } else {
q = q.Where("domain = ?", domain) q = q.Where("domain = ?", domain)
} }
// don't count the instance account or suspended users // don't count the instance account or suspended users
q = q.Where("username != ?", domain).Where("? IS NULL", pg.Ident("suspended_at")) q = q.
Where("username != ?", domain).
Where("? IS NULL", bun.Ident("suspended_at"))
return q.Count() count, err := q.Count(ctx)
return count, processErrorResponse(err)
} }
func (i *instanceDB) CountInstanceStatuses(domain string) (int, db.Error) { func (i *instanceDB) CountInstanceStatuses(ctx context.Context, domain string) (int, db.Error) {
q := i.conn.Model(&[]*gtsmodel.Status{}) q := i.conn.
NewSelect().
Model(&[]*gtsmodel.Status{})
if domain == i.config.Host { if domain == i.config.Host {
// if the domain is *this* domain, just count where local is true // if the domain is *this* domain, just count where local is true
@ -63,30 +70,39 @@ func (i *instanceDB) CountInstanceStatuses(domain string) (int, db.Error) {
Where("account.domain = ?", domain) Where("account.domain = ?", domain)
} }
return q.Count() count, err := q.Count(ctx)
return count, processErrorResponse(err)
} }
func (i *instanceDB) CountInstanceDomains(domain string) (int, db.Error) { func (i *instanceDB) CountInstanceDomains(ctx context.Context, domain string) (int, db.Error) {
q := i.conn.Model(&[]*gtsmodel.Instance{}) q := i.conn.
NewSelect().
Model(&[]*gtsmodel.Instance{})
if domain == i.config.Host { if domain == i.config.Host {
// if the domain is *this* domain, just count other instances it knows about // if the domain is *this* domain, just count other instances it knows about
// exclude domains that are blocked // exclude domains that are blocked
q = q.Where("domain != ?", domain).Where("? IS NULL", pg.Ident("suspended_at")) q = q.Where("domain != ?", domain).Where("? IS NULL", bun.Ident("suspended_at"))
} else { } else {
// TODO: implement federated domain counting properly for remote domains // TODO: implement federated domain counting properly for remote domains
return 0, nil return 0, nil
} }
return q.Count() count, err := q.Count(ctx)
return count, processErrorResponse(err)
} }
func (i *instanceDB) GetInstanceAccounts(domain string, maxID string, limit int) ([]*gtsmodel.Account, db.Error) { func (i *instanceDB) GetInstanceAccounts(ctx context.Context, domain string, maxID string, limit int) ([]*gtsmodel.Account, db.Error) {
i.log.Debug("GetAccountsForInstance") i.log.Debug("GetAccountsForInstance")
accounts := []*gtsmodel.Account{} accounts := []*gtsmodel.Account{}
q := i.conn.Model(&accounts).Where("domain = ?", domain).Order("id DESC") q := i.conn.NewSelect().
Model(&accounts).
Where("domain = ?", domain).
Order("id DESC")
if maxID != "" { if maxID != "" {
q = q.Where("id < ?", maxID) q = q.Where("id < ?", maxID)
@ -96,17 +112,7 @@ func (i *instanceDB) GetInstanceAccounts(domain string, maxID string, limit int)
q = q.Limit(limit) q = q.Limit(limit)
} }
err := q.Select() err := processErrorResponse(q.Scan(ctx))
if err != nil {
if err == pg.ErrNoRows {
return nil, db.ErrNoEntries
}
return nil, err
}
if len(accounts) == 0 { return accounts, err
return nil, db.ErrNoEntries
}
return accounts, nil
} }

View file

@ -16,38 +16,38 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package pg package bundb
import ( import (
"context" "context"
"github.com/go-pg/pg/v10"
"github.com/go-pg/pg/v10/orm"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/uptrace/bun"
) )
type mediaDB struct { type mediaDB struct {
config *config.Config config *config.Config
conn *pg.DB conn *bun.DB
log *logrus.Logger log *logrus.Logger
cancel context.CancelFunc
} }
func (m *mediaDB) newMediaQ(i interface{}) *orm.Query { func (m *mediaDB) newMediaQ(i interface{}) *bun.SelectQuery {
return m.conn.Model(i). return m.conn.
NewSelect().
Model(i).
Relation("Account") Relation("Account")
} }
func (m *mediaDB) GetAttachmentByID(id string) (*gtsmodel.MediaAttachment, db.Error) { func (m *mediaDB) GetAttachmentByID(ctx context.Context, id string) (*gtsmodel.MediaAttachment, db.Error) {
attachment := &gtsmodel.MediaAttachment{} attachment := &gtsmodel.MediaAttachment{}
q := m.newMediaQ(attachment). q := m.newMediaQ(attachment).
Where("media_attachment.id = ?", id) Where("media_attachment.id = ?", id)
err := processErrorResponse(q.Select()) err := processErrorResponse(q.Scan(ctx))
return attachment, err return attachment, err
} }

View file

@ -16,25 +16,23 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package pg package bundb
import ( import (
"context" "context"
"github.com/go-pg/pg/v10"
"github.com/go-pg/pg/v10/orm"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/superseriousbusiness/gotosocial/internal/cache" "github.com/superseriousbusiness/gotosocial/internal/cache"
"github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/uptrace/bun"
) )
type mentionDB struct { type mentionDB struct {
config *config.Config config *config.Config
conn *pg.DB conn *bun.DB
log *logrus.Logger log *logrus.Logger
cancel context.CancelFunc
cache cache.Cache cache cache.Cache
} }
@ -67,14 +65,16 @@ func (m *mentionDB) mentionCached(id string) (*gtsmodel.Mention, bool) {
return mention, true return mention, true
} }
func (m *mentionDB) newMentionQ(i interface{}) *orm.Query { func (m *mentionDB) newMentionQ(i interface{}) *bun.SelectQuery {
return m.conn.Model(i). return m.conn.
NewSelect().
Model(i).
Relation("Status"). Relation("Status").
Relation("OriginAccount"). Relation("OriginAccount").
Relation("TargetAccount") Relation("TargetAccount")
} }
func (m *mentionDB) GetMention(id string) (*gtsmodel.Mention, db.Error) { func (m *mentionDB) GetMention(ctx context.Context, id string) (*gtsmodel.Mention, db.Error) {
if mention, cached := m.mentionCached(id); cached { if mention, cached := m.mentionCached(id); cached {
return mention, nil return mention, nil
} }
@ -84,7 +84,7 @@ func (m *mentionDB) GetMention(id string) (*gtsmodel.Mention, db.Error) {
q := m.newMentionQ(mention). q := m.newMentionQ(mention).
Where("mention.id = ?", id) Where("mention.id = ?", id)
err := processErrorResponse(q.Select()) err := processErrorResponse(q.Scan(ctx))
if err == nil && mention != nil { if err == nil && mention != nil {
m.cacheMention(id, mention) m.cacheMention(id, mention)
@ -93,11 +93,11 @@ func (m *mentionDB) GetMention(id string) (*gtsmodel.Mention, db.Error) {
return mention, err return mention, err
} }
func (m *mentionDB) GetMentions(ids []string) ([]*gtsmodel.Mention, db.Error) { func (m *mentionDB) GetMentions(ctx context.Context, ids []string) ([]*gtsmodel.Mention, db.Error) {
mentions := []*gtsmodel.Mention{} mentions := []*gtsmodel.Mention{}
for _, i := range ids { for _, i := range ids {
mention, err := m.GetMention(i) mention, err := m.GetMention(ctx, i)
if err != nil { if err != nil {
return nil, processErrorResponse(err) return nil, processErrorResponse(err)
} }

View file

@ -16,25 +16,23 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package pg package bundb
import ( import (
"context" "context"
"github.com/go-pg/pg/v10"
"github.com/go-pg/pg/v10/orm"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/superseriousbusiness/gotosocial/internal/cache" "github.com/superseriousbusiness/gotosocial/internal/cache"
"github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/uptrace/bun"
) )
type notificationDB struct { type notificationDB struct {
config *config.Config config *config.Config
conn *pg.DB conn *bun.DB
log *logrus.Logger log *logrus.Logger
cancel context.CancelFunc
cache cache.Cache cache cache.Cache
} }
@ -67,14 +65,16 @@ func (n *notificationDB) notificationCached(id string) (*gtsmodel.Notification,
return notification, true return notification, true
} }
func (n *notificationDB) newNotificationQ(i interface{}) *orm.Query { func (n *notificationDB) newNotificationQ(i interface{}) *bun.SelectQuery {
return n.conn.Model(i). return n.conn.
NewSelect().
Model(i).
Relation("OriginAccount"). Relation("OriginAccount").
Relation("TargetAccount"). Relation("TargetAccount").
Relation("Status") Relation("Status")
} }
func (n *notificationDB) GetNotification(id string) (*gtsmodel.Notification, db.Error) { func (n *notificationDB) GetNotification(ctx context.Context, id string) (*gtsmodel.Notification, db.Error) {
if notification, cached := n.notificationCached(id); cached { if notification, cached := n.notificationCached(id); cached {
return notification, nil return notification, nil
} }
@ -84,7 +84,7 @@ func (n *notificationDB) GetNotification(id string) (*gtsmodel.Notification, db.
q := n.newNotificationQ(notification). q := n.newNotificationQ(notification).
Where("notification.id = ?", id) Where("notification.id = ?", id)
err := processErrorResponse(q.Select()) err := processErrorResponse(q.Scan(ctx))
if err == nil && notification != nil { if err == nil && notification != nil {
n.cacheNotification(id, notification) n.cacheNotification(id, notification)
@ -93,10 +93,11 @@ func (n *notificationDB) GetNotification(id string) (*gtsmodel.Notification, db.
return notification, err return notification, err
} }
func (n *notificationDB) GetNotifications(accountID string, limit int, maxID string, sinceID string) ([]*gtsmodel.Notification, db.Error) { func (n *notificationDB) GetNotifications(ctx context.Context, accountID string, limit int, maxID string, sinceID string) ([]*gtsmodel.Notification, db.Error) {
// begin by selecting just the IDs // begin by selecting just the IDs
notifIDs := []*gtsmodel.Notification{} notifIDs := []*gtsmodel.Notification{}
q := n.conn. q := n.conn.
NewSelect().
Model(&notifIDs). Model(&notifIDs).
Column("id"). Column("id").
Where("target_account_id = ?", accountID). Where("target_account_id = ?", accountID).
@ -114,7 +115,7 @@ func (n *notificationDB) GetNotifications(accountID string, limit int, maxID str
q = q.Limit(limit) q = q.Limit(limit)
} }
err := processErrorResponse(q.Select()) err := processErrorResponse(q.Scan(ctx))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -123,7 +124,7 @@ func (n *notificationDB) GetNotifications(accountID string, limit int, maxID str
// reason for this is that for each notif, we can instead get it from our cache if it's cached // reason for this is that for each notif, we can instead get it from our cache if it's cached
notifications := []*gtsmodel.Notification{} notifications := []*gtsmodel.Notification{}
for _, notifID := range notifIDs { for _, notifID := range notifIDs {
notif, err := n.GetNotification(notifID.ID) notif, err := n.GetNotification(ctx, notifID.ID)
errP := processErrorResponse(err) errP := processErrorResponse(err)
if errP != nil { if errP != nil {
return nil, errP return nil, errP

View file

@ -16,44 +16,49 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package pg package bundb
import ( import (
"context" "context"
"database/sql"
"fmt" "fmt"
"github.com/go-pg/pg/v10"
"github.com/go-pg/pg/v10/orm"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/uptrace/bun"
) )
type relationshipDB struct { type relationshipDB struct {
config *config.Config config *config.Config
conn *pg.DB conn *bun.DB
log *logrus.Logger log *logrus.Logger
cancel context.CancelFunc
} }
func (r *relationshipDB) newBlockQ(block *gtsmodel.Block) *orm.Query { func (r *relationshipDB) newBlockQ(block *gtsmodel.Block) *bun.SelectQuery {
return r.conn.Model(block). return r.conn.
NewSelect().
Model(block).
Relation("Account"). Relation("Account").
Relation("TargetAccount") Relation("TargetAccount")
} }
func (r *relationshipDB) newFollowQ(follow interface{}) *orm.Query { func (r *relationshipDB) newFollowQ(follow interface{}) *bun.SelectQuery {
return r.conn.Model(follow). return r.conn.
NewSelect().
Model(follow).
Relation("Account"). Relation("Account").
Relation("TargetAccount") Relation("TargetAccount")
} }
func (r *relationshipDB) IsBlocked(account1 string, account2 string, eitherDirection bool) (bool, db.Error) { func (r *relationshipDB) IsBlocked(ctx context.Context, account1 string, account2 string, eitherDirection bool) (bool, db.Error) {
q := r.conn. q := r.conn.
NewSelect().
Model(&gtsmodel.Block{}). Model(&gtsmodel.Block{}).
Where("account_id = ?", account1). Where("account_id = ?", account1).
Where("target_account_id = ?", account2) Where("target_account_id = ?", account2).
Limit(1)
if eitherDirection { if eitherDirection {
q = q. q = q.
@ -61,30 +66,36 @@ func (r *relationshipDB) IsBlocked(account1 string, account2 string, eitherDirec
Where("account_id = ?", account2) Where("account_id = ?", account2)
} }
return q.Exists() return exists(ctx, q)
} }
func (r *relationshipDB) GetBlock(account1 string, account2 string) (*gtsmodel.Block, db.Error) { func (r *relationshipDB) GetBlock(ctx context.Context, account1 string, account2 string) (*gtsmodel.Block, db.Error) {
block := &gtsmodel.Block{} block := &gtsmodel.Block{}
q := r.newBlockQ(block). q := r.newBlockQ(block).
Where("block.account_id = ?", account1). Where("block.account_id = ?", account1).
Where("block.target_account_id = ?", account2) Where("block.target_account_id = ?", account2)
err := processErrorResponse(q.Select()) err := processErrorResponse(q.Scan(ctx))
return block, err return block, err
} }
func (r *relationshipDB) GetRelationship(requestingAccount string, targetAccount string) (*gtsmodel.Relationship, db.Error) { func (r *relationshipDB) GetRelationship(ctx context.Context, requestingAccount string, targetAccount string) (*gtsmodel.Relationship, db.Error) {
rel := &gtsmodel.Relationship{ rel := &gtsmodel.Relationship{
ID: targetAccount, ID: targetAccount,
} }
// check if the requesting account follows the target account // check if the requesting account follows the target account
follow := &gtsmodel.Follow{} follow := &gtsmodel.Follow{}
if err := r.conn.Model(follow).Where("account_id = ?", requestingAccount).Where("target_account_id = ?", targetAccount).Select(); err != nil { if err := r.conn.
if err != pg.ErrNoRows { NewSelect().
Model(follow).
Where("account_id = ?", requestingAccount).
Where("target_account_id = ?", targetAccount).
Limit(1).
Scan(ctx); err != nil {
if err != sql.ErrNoRows {
// a proper error // a proper error
return nil, fmt.Errorf("getrelationship: error checking follow existence: %s", err) return nil, fmt.Errorf("getrelationship: error checking follow existence: %s", err)
} }
@ -100,75 +111,101 @@ func (r *relationshipDB) GetRelationship(requestingAccount string, targetAccount
} }
// check if the target account follows the requesting account // check if the target account follows the requesting account
followedBy, err := r.conn.Model(&gtsmodel.Follow{}).Where("account_id = ?", targetAccount).Where("target_account_id = ?", requestingAccount).Exists() count, err := r.conn.
NewSelect().
Model(&gtsmodel.Follow{}).
Where("account_id = ?", targetAccount).
Where("target_account_id = ?", requestingAccount).
Limit(1).
Count(ctx)
if err != nil { if err != nil {
return nil, fmt.Errorf("getrelationship: error checking followed_by existence: %s", err) return nil, fmt.Errorf("getrelationship: error checking followed_by existence: %s", err)
} }
rel.FollowedBy = followedBy rel.FollowedBy = count > 0
// check if the requesting account blocks the target account // check if the requesting account blocks the target account
blocking, err := r.conn.Model(&gtsmodel.Block{}).Where("account_id = ?", requestingAccount).Where("target_account_id = ?", targetAccount).Exists() count, err = r.conn.NewSelect().
Model(&gtsmodel.Block{}).
Where("account_id = ?", requestingAccount).
Where("target_account_id = ?", targetAccount).
Limit(1).
Count(ctx)
if err != nil { if err != nil {
return nil, fmt.Errorf("getrelationship: error checking blocking existence: %s", err) return nil, fmt.Errorf("getrelationship: error checking blocking existence: %s", err)
} }
rel.Blocking = blocking rel.Blocking = count > 0
// check if the target account blocks the requesting account // check if the target account blocks the requesting account
blockedBy, err := r.conn.Model(&gtsmodel.Block{}).Where("account_id = ?", targetAccount).Where("target_account_id = ?", requestingAccount).Exists() count, err = r.conn.
NewSelect().
Model(&gtsmodel.Block{}).
Where("account_id = ?", targetAccount).
Where("target_account_id = ?", requestingAccount).
Limit(1).
Count(ctx)
if err != nil { if err != nil {
return nil, fmt.Errorf("getrelationship: error checking blocked existence: %s", err) return nil, fmt.Errorf("getrelationship: error checking blocked existence: %s", err)
} }
rel.BlockedBy = blockedBy rel.BlockedBy = count > 0
// check if there's a pending following request from requesting account to target account // check if there's a pending following request from requesting account to target account
requested, err := r.conn.Model(&gtsmodel.FollowRequest{}).Where("account_id = ?", requestingAccount).Where("target_account_id = ?", targetAccount).Exists() count, err = r.conn.
NewSelect().
Model(&gtsmodel.FollowRequest{}).
Where("account_id = ?", requestingAccount).
Where("target_account_id = ?", targetAccount).
Limit(1).
Count(ctx)
if err != nil { if err != nil {
return nil, fmt.Errorf("getrelationship: error checking blocked existence: %s", err) return nil, fmt.Errorf("getrelationship: error checking blocked existence: %s", err)
} }
rel.Requested = requested rel.Requested = count > 0
return rel, nil return rel, nil
} }
func (r *relationshipDB) IsFollowing(sourceAccount *gtsmodel.Account, targetAccount *gtsmodel.Account) (bool, db.Error) { func (r *relationshipDB) IsFollowing(ctx context.Context, sourceAccount *gtsmodel.Account, targetAccount *gtsmodel.Account) (bool, db.Error) {
if sourceAccount == nil || targetAccount == nil { if sourceAccount == nil || targetAccount == nil {
return false, nil return false, nil
} }
q := r.conn. q := r.conn.
NewSelect().
Model(&gtsmodel.Follow{}). Model(&gtsmodel.Follow{}).
Where("account_id = ?", sourceAccount.ID). Where("account_id = ?", sourceAccount.ID).
Where("target_account_id = ?", targetAccount.ID) Where("target_account_id = ?", targetAccount.ID).
Limit(1)
return q.Exists() return exists(ctx, q)
} }
func (r *relationshipDB) IsFollowRequested(sourceAccount *gtsmodel.Account, targetAccount *gtsmodel.Account) (bool, db.Error) { func (r *relationshipDB) IsFollowRequested(ctx context.Context, sourceAccount *gtsmodel.Account, targetAccount *gtsmodel.Account) (bool, db.Error) {
if sourceAccount == nil || targetAccount == nil { if sourceAccount == nil || targetAccount == nil {
return false, nil return false, nil
} }
q := r.conn. q := r.conn.
NewSelect().
Model(&gtsmodel.FollowRequest{}). Model(&gtsmodel.FollowRequest{}).
Where("account_id = ?", sourceAccount.ID). Where("account_id = ?", sourceAccount.ID).
Where("target_account_id = ?", targetAccount.ID) Where("target_account_id = ?", targetAccount.ID)
return q.Exists() return exists(ctx, q)
} }
func (r *relationshipDB) IsMutualFollowing(account1 *gtsmodel.Account, account2 *gtsmodel.Account) (bool, db.Error) { func (r *relationshipDB) IsMutualFollowing(ctx context.Context, account1 *gtsmodel.Account, account2 *gtsmodel.Account) (bool, db.Error) {
if account1 == nil || account2 == nil { if account1 == nil || account2 == nil {
return false, nil return false, nil
} }
// make sure account 1 follows account 2 // make sure account 1 follows account 2
f1, err := r.IsFollowing(account1, account2) f1, err := r.IsFollowing(ctx, account1, account2)
if err != nil { if err != nil {
return false, processErrorResponse(err) return false, processErrorResponse(err)
} }
// make sure account 2 follows account 1 // make sure account 2 follows account 1
f2, err := r.IsFollowing(account2, account1) f2, err := r.IsFollowing(ctx, account2, account1)
if err != nil { if err != nil {
return false, processErrorResponse(err) return false, processErrorResponse(err)
} }
@ -176,14 +213,16 @@ func (r *relationshipDB) IsMutualFollowing(account1 *gtsmodel.Account, account2
return f1 && f2, nil return f1 && f2, nil
} }
func (r *relationshipDB) AcceptFollowRequest(originAccountID string, targetAccountID string) (*gtsmodel.Follow, db.Error) { func (r *relationshipDB) AcceptFollowRequest(ctx context.Context, originAccountID string, targetAccountID string) (*gtsmodel.Follow, db.Error) {
// make sure the original follow request exists // make sure the original follow request exists
fr := &gtsmodel.FollowRequest{} fr := &gtsmodel.FollowRequest{}
if err := r.conn.Model(fr).Where("account_id = ?", originAccountID).Where("target_account_id = ?", targetAccountID).Select(); err != nil { if err := r.conn.
if err == pg.ErrMultiRows { NewSelect().
return nil, db.ErrNoEntries Model(fr).
} Where("account_id = ?", originAccountID).
return nil, err Where("target_account_id = ?", targetAccountID).
Scan(ctx); err != nil {
return nil, processErrorResponse(err)
} }
// create a new follow to 'replace' the request with // create a new follow to 'replace' the request with
@ -195,82 +234,95 @@ func (r *relationshipDB) AcceptFollowRequest(originAccountID string, targetAccou
} }
// if the follow already exists, just update the URI -- we don't need to do anything else // if the follow already exists, just update the URI -- we don't need to do anything else
if _, err := r.conn.Model(follow).OnConflict("ON CONSTRAINT follows_account_id_target_account_id_key DO UPDATE set uri = ?", follow.URI).Insert(); err != nil { if _, err := r.conn.
return nil, err NewInsert().
Model(follow).
On("CONFLICT CONSTRAINT follows_account_id_target_account_id_key DO UPDATE set uri = ?", follow.URI).
Exec(ctx); err != nil {
return nil, processErrorResponse(err)
} }
// now remove the follow request // now remove the follow request
if _, err := r.conn.Model(&gtsmodel.FollowRequest{}).Where("account_id = ?", originAccountID).Where("target_account_id = ?", targetAccountID).Delete(); err != nil { if _, err := r.conn.
return nil, err NewDelete().
Model(&gtsmodel.FollowRequest{}).
Where("account_id = ?", originAccountID).
Where("target_account_id = ?", targetAccountID).
Exec(ctx); err != nil {
return nil, processErrorResponse(err)
} }
return follow, nil return follow, nil
} }
func (r *relationshipDB) GetAccountFollowRequests(accountID string) ([]*gtsmodel.FollowRequest, db.Error) { func (r *relationshipDB) GetAccountFollowRequests(ctx context.Context, accountID string) ([]*gtsmodel.FollowRequest, db.Error) {
followRequests := []*gtsmodel.FollowRequest{} followRequests := []*gtsmodel.FollowRequest{}
q := r.newFollowQ(&followRequests). q := r.newFollowQ(&followRequests).
Where("target_account_id = ?", accountID) Where("target_account_id = ?", accountID)
err := processErrorResponse(q.Select()) err := processErrorResponse(q.Scan(ctx))
return followRequests, err return followRequests, err
} }
func (r *relationshipDB) GetAccountFollows(accountID string) ([]*gtsmodel.Follow, db.Error) { func (r *relationshipDB) GetAccountFollows(ctx context.Context, accountID string) ([]*gtsmodel.Follow, db.Error) {
follows := []*gtsmodel.Follow{} follows := []*gtsmodel.Follow{}
q := r.newFollowQ(&follows). q := r.newFollowQ(&follows).
Where("account_id = ?", accountID) Where("account_id = ?", accountID)
err := processErrorResponse(q.Select()) err := processErrorResponse(q.Scan(ctx))
return follows, err return follows, err
} }
func (r *relationshipDB) CountAccountFollows(accountID string, localOnly bool) (int, db.Error) { func (r *relationshipDB) CountAccountFollows(ctx context.Context, accountID string, localOnly bool) (int, db.Error) {
return r.conn. return r.conn.
NewSelect().
Model(&[]*gtsmodel.Follow{}). Model(&[]*gtsmodel.Follow{}).
Where("account_id = ?", accountID). Where("account_id = ?", accountID).
Count() Count(ctx)
} }
func (r *relationshipDB) GetAccountFollowedBy(accountID string, localOnly bool) ([]*gtsmodel.Follow, db.Error) { func (r *relationshipDB) GetAccountFollowedBy(ctx context.Context, accountID string, localOnly bool) ([]*gtsmodel.Follow, db.Error) {
follows := []*gtsmodel.Follow{} follows := []*gtsmodel.Follow{}
q := r.conn.Model(&follows) q := r.conn.
NewSelect().
Model(&follows)
if localOnly { if localOnly {
// for local accounts let's get where domain is null OR where domain is an empty string, just to be safe // for local accounts let's get where domain is null OR where domain is an empty string, just to be safe
whereGroup := func(q *pg.Query) (*pg.Query, error) { whereGroup := func(q *bun.SelectQuery) *bun.SelectQuery {
q = q. q = q.
WhereOr("? IS NULL", pg.Ident("a.domain")). WhereOr("? IS NULL", bun.Ident("a.domain")).
WhereOr("a.domain = ?", "") WhereOr("a.domain = ?", "")
return q, nil return q
} }
q = q.ColumnExpr("follow.*"). q = q.ColumnExpr("follow.*").
Join("JOIN accounts AS a ON follow.account_id = TEXT(a.id)"). Join("JOIN accounts AS a ON follow.account_id = TEXT(a.id)").
Where("follow.target_account_id = ?", accountID). Where("follow.target_account_id = ?", accountID).
WhereGroup(whereGroup) WhereGroup(" AND ", whereGroup)
} else { } else {
q = q.Where("target_account_id = ?", accountID) q = q.Where("target_account_id = ?", accountID)
} }
if err := q.Select(); err != nil { if err := q.Scan(ctx); err != nil {
if err == pg.ErrNoRows { if err == sql.ErrNoRows {
return follows, nil return follows, nil
} }
return nil, err return nil, processErrorResponse(err)
} }
return follows, nil return follows, nil
} }
func (r *relationshipDB) CountAccountFollowedBy(accountID string, localOnly bool) (int, db.Error) { func (r *relationshipDB) CountAccountFollowedBy(ctx context.Context, accountID string, localOnly bool) (int, db.Error) {
return r.conn. return r.conn.
NewSelect().
Model(&[]*gtsmodel.Follow{}). Model(&[]*gtsmodel.Follow{}).
Where("target_account_id = ?", accountID). Where("target_account_id = ?", accountID).
Count() Count(ctx)
} }

View file

@ -0,0 +1,85 @@
/*
GoToSocial
Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package bundb
import (
"context"
"crypto/rand"
"github.com/sirupsen/logrus"
"github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/id"
"github.com/uptrace/bun"
)
type sessionDB struct {
config *config.Config
conn *bun.DB
log *logrus.Logger
}
func (s *sessionDB) GetSession(ctx context.Context) (*gtsmodel.RouterSession, db.Error) {
rs := new(gtsmodel.RouterSession)
q := s.conn.
NewSelect().
Model(rs).
Limit(1)
_, err := q.Exec(ctx)
err = processErrorResponse(err)
return rs, err
}
func (s *sessionDB) CreateSession(ctx context.Context) (*gtsmodel.RouterSession, db.Error) {
auth := make([]byte, 32)
crypt := make([]byte, 32)
if _, err := rand.Read(auth); err != nil {
return nil, err
}
if _, err := rand.Read(crypt); err != nil {
return nil, err
}
rid, err := id.NewULID()
if err != nil {
return nil, err
}
rs := &gtsmodel.RouterSession{
ID: rid,
Auth: auth,
Crypt: crypt,
}
q := s.conn.
NewInsert().
Model(rs)
_, err = q.Exec(ctx)
err = processErrorResponse(err)
return rs, err
}

375
internal/db/bundb/status.go Normal file
View file

@ -0,0 +1,375 @@
/*
GoToSocial
Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package bundb
import (
"container/list"
"context"
"errors"
"time"
"github.com/sirupsen/logrus"
"github.com/superseriousbusiness/gotosocial/internal/cache"
"github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/uptrace/bun"
)
type statusDB struct {
config *config.Config
conn *bun.DB
log *logrus.Logger
cache cache.Cache
}
func (s *statusDB) cacheStatus(id string, status *gtsmodel.Status) {
if s.cache == nil {
s.cache = cache.New()
}
if err := s.cache.Store(id, status); err != nil {
s.log.Panicf("statusDB: error storing in cache: %s", err)
}
}
func (s *statusDB) statusCached(id string) (*gtsmodel.Status, bool) {
if s.cache == nil {
s.cache = cache.New()
return nil, false
}
sI, err := s.cache.Fetch(id)
if err != nil || sI == nil {
return nil, false
}
status, ok := sI.(*gtsmodel.Status)
if !ok {
s.log.Panicf("statusDB: cached interface with key %s was not a status", id)
}
return status, true
}
func (s *statusDB) newStatusQ(status interface{}) *bun.SelectQuery {
return s.conn.
NewSelect().
Model(status).
Relation("Attachments").
Relation("Tags").
Relation("Mentions").
Relation("Emojis").
Relation("Account").
Relation("InReplyToAccount").
Relation("BoostOfAccount").
Relation("CreatedWithApplication")
}
func (s *statusDB) getAttachedStatuses(ctx context.Context, status *gtsmodel.Status) *gtsmodel.Status {
if status.InReplyToID != "" && status.InReplyTo == nil {
if inReplyTo, cached := s.statusCached(status.InReplyToID); cached {
status.InReplyTo = inReplyTo
} else if inReplyTo, err := s.GetStatusByID(ctx, status.InReplyToID); err == nil {
status.InReplyTo = inReplyTo
}
}
if status.BoostOfID != "" && status.BoostOf == nil {
if boostOf, cached := s.statusCached(status.BoostOfID); cached {
status.BoostOf = boostOf
} else if boostOf, err := s.GetStatusByID(ctx, status.BoostOfID); err == nil {
status.BoostOf = boostOf
}
}
return status
}
func (s *statusDB) newFaveQ(faves interface{}) *bun.SelectQuery {
return s.conn.
NewSelect().
Model(faves).
Relation("Account").
Relation("TargetAccount").
Relation("Status")
}
func (s *statusDB) GetStatusByID(ctx context.Context, id string) (*gtsmodel.Status, db.Error) {
if status, cached := s.statusCached(id); cached {
return status, nil
}
status := new(gtsmodel.Status)
q := s.newStatusQ(status).
Where("status.id = ?", id)
err := processErrorResponse(q.Scan(ctx))
if err != nil {
return nil, err
}
if status != nil {
s.cacheStatus(id, status)
}
return s.getAttachedStatuses(ctx, status), err
}
func (s *statusDB) GetStatusByURI(ctx context.Context, uri string) (*gtsmodel.Status, db.Error) {
if status, cached := s.statusCached(uri); cached {
return status, nil
}
status := &gtsmodel.Status{}
q := s.newStatusQ(status).
Where("LOWER(status.uri) = LOWER(?)", uri)
err := processErrorResponse(q.Scan(ctx))
if err != nil {
return nil, err
}
if status != nil {
s.cacheStatus(uri, status)
}
return s.getAttachedStatuses(ctx, status), err
}
func (s *statusDB) GetStatusByURL(ctx context.Context, uri string) (*gtsmodel.Status, db.Error) {
if status, cached := s.statusCached(uri); cached {
return status, nil
}
status := &gtsmodel.Status{}
q := s.newStatusQ(status).
Where("LOWER(status.url) = LOWER(?)", uri)
err := processErrorResponse(q.Scan(ctx))
if err != nil {
return nil, err
}
if status != nil {
s.cacheStatus(uri, status)
}
return s.getAttachedStatuses(ctx, status), err
}
func (s *statusDB) PutStatus(ctx context.Context, status *gtsmodel.Status) db.Error {
transaction := func(ctx context.Context, tx bun.Tx) error {
// create links between this status and any emojis it uses
for _, i := range status.EmojiIDs {
if _, err := tx.NewInsert().Model(&gtsmodel.StatusToEmoji{
StatusID: status.ID,
EmojiID: i,
}).Exec(ctx); err != nil {
return err
}
}
// create links between this status and any tags it uses
for _, i := range status.TagIDs {
if _, err := tx.NewInsert().Model(&gtsmodel.StatusToTag{
StatusID: status.ID,
TagID: i,
}).Exec(ctx); err != nil {
return err
}
}
// change the status ID of the media attachments to the new status
for _, a := range status.Attachments {
a.StatusID = status.ID
a.UpdatedAt = time.Now()
if _, err := s.conn.NewUpdate().Model(a).
Where("id = ?", a.ID).
Exec(ctx); err != nil {
return err
}
}
_, err := tx.NewInsert().Model(status).Exec(ctx)
return err
}
return processErrorResponse(s.conn.RunInTx(ctx, nil, transaction))
}
func (s *statusDB) GetStatusParents(ctx context.Context, status *gtsmodel.Status, onlyDirect bool) ([]*gtsmodel.Status, db.Error) {
parents := []*gtsmodel.Status{}
s.statusParent(ctx, status, &parents, onlyDirect)
return parents, nil
}
func (s *statusDB) statusParent(ctx context.Context, status *gtsmodel.Status, foundStatuses *[]*gtsmodel.Status, onlyDirect bool) {
if status.InReplyToID == "" {
return
}
parentStatus, err := s.GetStatusByID(ctx, status.InReplyToID)
if err == nil {
*foundStatuses = append(*foundStatuses, parentStatus)
}
if onlyDirect {
return
}
s.statusParent(ctx, parentStatus, foundStatuses, false)
}
func (s *statusDB) GetStatusChildren(ctx context.Context, status *gtsmodel.Status, onlyDirect bool, minID string) ([]*gtsmodel.Status, db.Error) {
foundStatuses := &list.List{}
foundStatuses.PushFront(status)
s.statusChildren(ctx, status, foundStatuses, onlyDirect, minID)
children := []*gtsmodel.Status{}
for e := foundStatuses.Front(); e != nil; e = e.Next() {
entry, ok := e.Value.(*gtsmodel.Status)
if !ok {
panic(errors.New("entry in foundStatuses was not a *gtsmodel.Status"))
}
// only append children, not the overall parent status
if entry.ID != status.ID {
children = append(children, entry)
}
}
return children, nil
}
func (s *statusDB) statusChildren(ctx context.Context, status *gtsmodel.Status, foundStatuses *list.List, onlyDirect bool, minID string) {
immediateChildren := []*gtsmodel.Status{}
q := s.conn.
NewSelect().
Model(&immediateChildren).
Where("in_reply_to_id = ?", status.ID)
if minID != "" {
q = q.Where("status.id > ?", minID)
}
if err := q.Scan(ctx); err != nil {
return
}
for _, child := range immediateChildren {
insertLoop:
for e := foundStatuses.Front(); e != nil; e = e.Next() {
entry, ok := e.Value.(*gtsmodel.Status)
if !ok {
panic(errors.New("entry in foundStatuses was not a *gtsmodel.Status"))
}
if child.InReplyToAccountID != "" && entry.ID == child.InReplyToID {
foundStatuses.InsertAfter(child, e)
break insertLoop
}
}
// only do one loop if we only want direct children
if onlyDirect {
return
}
s.statusChildren(ctx, child, foundStatuses, false, minID)
}
}
func (s *statusDB) CountStatusReplies(ctx context.Context, status *gtsmodel.Status) (int, db.Error) {
return s.conn.NewSelect().Model(&gtsmodel.Status{}).Where("in_reply_to_id = ?", status.ID).Count(ctx)
}
func (s *statusDB) CountStatusReblogs(ctx context.Context, status *gtsmodel.Status) (int, db.Error) {
return s.conn.NewSelect().Model(&gtsmodel.Status{}).Where("boost_of_id = ?", status.ID).Count(ctx)
}
func (s *statusDB) CountStatusFaves(ctx context.Context, status *gtsmodel.Status) (int, db.Error) {
return s.conn.NewSelect().Model(&gtsmodel.StatusFave{}).Where("status_id = ?", status.ID).Count(ctx)
}
func (s *statusDB) IsStatusFavedBy(ctx context.Context, status *gtsmodel.Status, accountID string) (bool, db.Error) {
q := s.conn.
NewSelect().
Model(&gtsmodel.StatusFave{}).
Where("status_id = ?", status.ID).
Where("account_id = ?", accountID)
return exists(ctx, q)
}
func (s *statusDB) IsStatusRebloggedBy(ctx context.Context, status *gtsmodel.Status, accountID string) (bool, db.Error) {
q := s.conn.
NewSelect().
Model(&gtsmodel.Status{}).
Where("boost_of_id = ?", status.ID).
Where("account_id = ?", accountID)
return exists(ctx, q)
}
func (s *statusDB) IsStatusMutedBy(ctx context.Context, status *gtsmodel.Status, accountID string) (bool, db.Error) {
q := s.conn.
NewSelect().
Model(&gtsmodel.StatusMute{}).
Where("status_id = ?", status.ID).
Where("account_id = ?", accountID)
return exists(ctx, q)
}
func (s *statusDB) IsStatusBookmarkedBy(ctx context.Context, status *gtsmodel.Status, accountID string) (bool, db.Error) {
q := s.conn.
NewSelect().
Model(&gtsmodel.StatusBookmark{}).
Where("status_id = ?", status.ID).
Where("account_id = ?", accountID)
return exists(ctx, q)
}
func (s *statusDB) GetStatusFaves(ctx context.Context, status *gtsmodel.Status) ([]*gtsmodel.StatusFave, db.Error) {
faves := []*gtsmodel.StatusFave{}
q := s.newFaveQ(&faves).
Where("status_id = ?", status.ID)
err := processErrorResponse(q.Scan(ctx))
return faves, err
}
func (s *statusDB) GetStatusReblogs(ctx context.Context, status *gtsmodel.Status) ([]*gtsmodel.Status, db.Error) {
reblogs := []*gtsmodel.Status{}
q := s.newStatusQ(&reblogs).
Where("boost_of_id = ?", status.ID)
err := processErrorResponse(q.Scan(ctx))
return reblogs, err
}

View file

@ -16,9 +16,10 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package pg_test package bundb_test
import ( import (
"context"
"fmt" "fmt"
"testing" "testing"
"time" "time"
@ -28,7 +29,7 @@
) )
type StatusTestSuite struct { type StatusTestSuite struct {
PGStandardTestSuite BunDBStandardTestSuite
} }
func (suite *StatusTestSuite) SetupSuite() { func (suite *StatusTestSuite) SetupSuite() {
@ -56,8 +57,9 @@ func (suite *StatusTestSuite) TearDownTest() {
} }
func (suite *StatusTestSuite) TestGetStatusByID() { func (suite *StatusTestSuite) TestGetStatusByID() {
status, err := suite.db.GetStatusByID(suite.testStatuses["local_account_1_status_1"].ID) status, err := suite.db.GetStatusByID(context.Background(), suite.testStatuses["local_account_1_status_1"].ID)
if err != nil { if err != nil {
fmt.Println(err.Error())
suite.FailNow(err.Error()) suite.FailNow(err.Error())
} }
suite.NotNil(status) suite.NotNil(status)
@ -70,7 +72,7 @@ func (suite *StatusTestSuite) TestGetStatusByID() {
} }
func (suite *StatusTestSuite) TestGetStatusByURI() { func (suite *StatusTestSuite) TestGetStatusByURI() {
status, err := suite.db.GetStatusByURI(suite.testStatuses["local_account_1_status_1"].URI) status, err := suite.db.GetStatusByURI(context.Background(), suite.testStatuses["local_account_1_status_1"].URI)
if err != nil { if err != nil {
suite.FailNow(err.Error()) suite.FailNow(err.Error())
} }
@ -84,7 +86,7 @@ func (suite *StatusTestSuite) TestGetStatusByURI() {
} }
func (suite *StatusTestSuite) TestGetStatusWithExtras() { func (suite *StatusTestSuite) TestGetStatusWithExtras() {
status, err := suite.db.GetStatusByID(suite.testStatuses["admin_account_status_1"].ID) status, err := suite.db.GetStatusByID(context.Background(), suite.testStatuses["admin_account_status_1"].ID)
if err != nil { if err != nil {
suite.FailNow(err.Error()) suite.FailNow(err.Error())
} }
@ -97,7 +99,7 @@ func (suite *StatusTestSuite) TestGetStatusWithExtras() {
} }
func (suite *StatusTestSuite) TestGetStatusWithMention() { func (suite *StatusTestSuite) TestGetStatusWithMention() {
status, err := suite.db.GetStatusByID(suite.testStatuses["local_account_2_status_5"].ID) status, err := suite.db.GetStatusByID(context.Background(), suite.testStatuses["local_account_2_status_5"].ID)
if err != nil { if err != nil {
suite.FailNow(err.Error()) suite.FailNow(err.Error())
} }
@ -112,18 +114,18 @@ func (suite *StatusTestSuite) TestGetStatusWithMention() {
func (suite *StatusTestSuite) TestGetStatusTwice() { func (suite *StatusTestSuite) TestGetStatusTwice() {
before1 := time.Now() before1 := time.Now()
_, err := suite.db.GetStatusByURI(suite.testStatuses["local_account_1_status_1"].URI) _, err := suite.db.GetStatusByURI(context.Background(), suite.testStatuses["local_account_1_status_1"].URI)
suite.NoError(err) suite.NoError(err)
after1 := time.Now() after1 := time.Now()
duration1 := after1.Sub(before1) duration1 := after1.Sub(before1)
fmt.Println(duration1.Nanoseconds()) fmt.Println(duration1.Milliseconds())
before2 := time.Now() before2 := time.Now()
_, err = suite.db.GetStatusByURI(suite.testStatuses["local_account_1_status_1"].URI) _, err = suite.db.GetStatusByURI(context.Background(), suite.testStatuses["local_account_1_status_1"].URI)
suite.NoError(err) suite.NoError(err)
after2 := time.Now() after2 := time.Now()
duration2 := after2.Sub(before2) duration2 := after2.Sub(before2)
fmt.Println(duration2.Nanoseconds()) fmt.Println(duration2.Milliseconds())
// second retrieval should be several orders faster since it will be cached now // second retrieval should be several orders faster since it will be cached now
suite.Less(duration2, duration1) suite.Less(duration2, duration1)

View file

@ -16,43 +16,35 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package pg package bundb
import ( import (
"context" "context"
"database/sql"
"sort" "sort"
"github.com/go-pg/pg/v10"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/uptrace/bun"
) )
type timelineDB struct { type timelineDB struct {
config *config.Config config *config.Config
conn *pg.DB conn *bun.DB
log *logrus.Logger log *logrus.Logger
cancel context.CancelFunc
} }
func (t *timelineDB) GetHomeTimeline(accountID string, maxID string, sinceID string, minID string, limit int, local bool) ([]*gtsmodel.Status, db.Error) { func (t *timelineDB) GetHomeTimeline(ctx context.Context, accountID string, maxID string, sinceID string, minID string, limit int, local bool) ([]*gtsmodel.Status, db.Error) {
statuses := []*gtsmodel.Status{} statuses := []*gtsmodel.Status{}
q := t.conn.Model(&statuses) q := t.conn.
NewSelect().
Model(&statuses)
q = q.ColumnExpr("status.*"). q = q.ColumnExpr("status.*").
// Find out who accountID follows. // Find out who accountID follows.
Join("LEFT JOIN follows AS f ON f.target_account_id = status.account_id"). Join("LEFT JOIN follows AS f ON f.target_account_id = status.account_id").
// Use a WhereGroup here to specify that we want EITHER statuses posted by accounts that accountID follows,
// OR statuses posted by accountID itself (since a user should be able to see their own statuses).
//
// This is equivalent to something like WHERE ... AND (... OR ...)
// See: https://pg.uptrace.dev/queries/#select
WhereGroup(func(q *pg.Query) (*pg.Query, error) {
q = q.WhereOr("f.account_id = ?", accountID).
WhereOr("status.account_id = ?", accountID)
return q, nil
}).
// Sort by highest ID (newest) to lowest ID (oldest) // Sort by highest ID (newest) to lowest ID (oldest)
Order("status.id DESC") Order("status.id DESC")
@ -81,29 +73,32 @@ func (t *timelineDB) GetHomeTimeline(accountID string, maxID string, sinceID str
q = q.Limit(limit) q = q.Limit(limit)
} }
err := q.Select() // Use a WhereGroup here to specify that we want EITHER statuses posted by accounts that accountID follows,
if err != nil { // OR statuses posted by accountID itself (since a user should be able to see their own statuses).
if err == pg.ErrNoRows { //
return nil, db.ErrNoEntries // This is equivalent to something like WHERE ... AND (... OR ...)
} // See: https://bun.uptrace.dev/guide/queries.html#select
return nil, err whereGroup := func(*bun.SelectQuery) *bun.SelectQuery {
return q.
WhereOr("f.account_id = ?", accountID).
WhereOr("status.account_id = ?", accountID)
} }
if len(statuses) == 0 { q = q.WhereGroup(" AND ", whereGroup)
return nil, db.ErrNoEntries
}
return statuses, nil return statuses, processErrorResponse(q.Scan(ctx))
} }
func (t *timelineDB) GetPublicTimeline(accountID string, maxID string, sinceID string, minID string, limit int, local bool) ([]*gtsmodel.Status, db.Error) { func (t *timelineDB) GetPublicTimeline(ctx context.Context, accountID string, maxID string, sinceID string, minID string, limit int, local bool) ([]*gtsmodel.Status, db.Error) {
statuses := []*gtsmodel.Status{} statuses := []*gtsmodel.Status{}
q := t.conn.Model(&statuses). q := t.conn.
NewSelect().
Model(&statuses).
Where("visibility = ?", gtsmodel.VisibilityPublic). Where("visibility = ?", gtsmodel.VisibilityPublic).
Where("? IS NULL", pg.Ident("in_reply_to_id")). Where("? IS NULL", bun.Ident("in_reply_to_id")).
Where("? IS NULL", pg.Ident("in_reply_to_uri")). Where("? IS NULL", bun.Ident("in_reply_to_uri")).
Where("? IS NULL", pg.Ident("boost_of_id")). Where("? IS NULL", bun.Ident("boost_of_id")).
Order("status.id DESC") Order("status.id DESC")
if maxID != "" { if maxID != "" {
@ -126,28 +121,18 @@ func (t *timelineDB) GetPublicTimeline(accountID string, maxID string, sinceID s
q = q.Limit(limit) q = q.Limit(limit)
} }
err := q.Select() return statuses, processErrorResponse(q.Scan(ctx))
if err != nil {
if err == pg.ErrNoRows {
return nil, db.ErrNoEntries
}
return nil, err
}
if len(statuses) == 0 {
return nil, db.ErrNoEntries
}
return statuses, nil
} }
// TODO optimize this query and the logic here, because it's slow as balls -- it takes like a literal second to return with a limit of 20! // TODO optimize this query and the logic here, because it's slow as balls -- it takes like a literal second to return with a limit of 20!
// It might be worth serving it through a timeline instead of raw DB queries, like we do for Home feeds. // It might be worth serving it through a timeline instead of raw DB queries, like we do for Home feeds.
func (t *timelineDB) GetFavedTimeline(accountID string, maxID string, minID string, limit int) ([]*gtsmodel.Status, string, string, db.Error) { func (t *timelineDB) GetFavedTimeline(ctx context.Context, accountID string, maxID string, minID string, limit int) ([]*gtsmodel.Status, string, string, db.Error) {
faves := []*gtsmodel.StatusFave{} faves := []*gtsmodel.StatusFave{}
fq := t.conn.Model(&faves). fq := t.conn.
NewSelect().
Model(&faves).
Where("account_id = ?", accountID). Where("account_id = ?", accountID).
Order("id DESC") Order("id DESC")
@ -163,9 +148,9 @@ func (t *timelineDB) GetFavedTimeline(accountID string, maxID string, minID stri
fq = fq.Limit(limit) fq = fq.Limit(limit)
} }
err := fq.Select() err := fq.Scan(ctx)
if err != nil { if err != nil {
if err == pg.ErrNoRows { if err == sql.ErrNoRows {
return nil, "", "", db.ErrNoEntries return nil, "", "", db.ErrNoEntries
} }
return nil, "", "", err return nil, "", "", err
@ -185,9 +170,13 @@ func (t *timelineDB) GetFavedTimeline(accountID string, maxID string, minID stri
} }
statuses := []*gtsmodel.Status{} statuses := []*gtsmodel.Status{}
err = t.conn.Model(&statuses).Where("id IN (?)", pg.In(in)).Select() err = t.conn.
NewSelect().
Model(&statuses).
Where("id IN (?)", bun.In(in)).
Scan(ctx)
if err != nil { if err != nil {
if err == pg.ErrNoRows { if err == sql.ErrNoRows {
return nil, "", "", db.ErrNoEntries return nil, "", "", db.ErrNoEntries
} }
return nil, "", "", err return nil, "", "", err

78
internal/db/bundb/util.go Normal file
View file

@ -0,0 +1,78 @@
/*
GoToSocial
Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package bundb
import (
"context"
"strings"
"database/sql"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/uptrace/bun"
)
// processErrorResponse parses the given error and returns an appropriate DBError.
func processErrorResponse(err error) db.Error {
switch err {
case nil:
return nil
case sql.ErrNoRows:
return db.ErrNoEntries
default:
if strings.Contains(err.Error(), "duplicate key value violates unique constraint") {
return db.ErrAlreadyExists
}
return err
}
}
func exists(ctx context.Context, q *bun.SelectQuery) (bool, db.Error) {
count, err := q.Count(ctx)
exists := count != 0
err = processErrorResponse(err)
if err != nil {
if err == db.ErrNoEntries {
return false, nil
}
return false, err
}
return exists, nil
}
func notExists(ctx context.Context, q *bun.SelectQuery) (bool, db.Error) {
count, err := q.Count(ctx)
notExists := count == 0
err = processErrorResponse(err)
if err != nil {
if err == db.ErrNoEntries {
return true, nil
}
return false, err
}
return notExists, nil
}

View file

@ -19,6 +19,8 @@
package db package db
import ( import (
"context"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
) )
@ -38,6 +40,7 @@ type DB interface {
Mention Mention
Notification Notification
Relationship Relationship
Session
Status Status
Timeline Timeline
@ -52,7 +55,7 @@ type DB interface {
// //
// Note: this func doesn't/shouldn't do any manipulation of the accounts in the DB, it's just for checking // Note: this func doesn't/shouldn't do any manipulation of the accounts in the DB, it's just for checking
// if they exist in the db and conveniently returning them if they do. // if they exist in the db and conveniently returning them if they do.
MentionStringsToMentions(targetAccounts []string, originAccountID string, statusID string) ([]*gtsmodel.Mention, error) MentionStringsToMentions(ctx context.Context, targetAccounts []string, originAccountID string, statusID string) ([]*gtsmodel.Mention, error)
// TagStringsToTags takes a slice of deduplicated, lowercase tags in the form "somehashtag", which have been // TagStringsToTags takes a slice of deduplicated, lowercase tags in the form "somehashtag", which have been
// used in a status. It takes the id of the account that wrote the status, and the id of the status itself, and then // used in a status. It takes the id of the account that wrote the status, and the id of the status itself, and then
@ -61,7 +64,7 @@ type DB interface {
// //
// Note: this func doesn't/shouldn't do any manipulation of the tags in the DB, it's just for checking // Note: this func doesn't/shouldn't do any manipulation of the tags in the DB, it's just for checking
// if they exist in the db already, and conveniently returning them, or creating new tag structs. // if they exist in the db already, and conveniently returning them, or creating new tag structs.
TagStringsToTags(tags []string, originAccountID string, statusID string) ([]*gtsmodel.Tag, error) TagStringsToTags(ctx context.Context, tags []string, originAccountID string, statusID string) ([]*gtsmodel.Tag, error)
// EmojiStringsToEmojis takes a slice of deduplicated, lowercase emojis in the form ":emojiname:", which have been // EmojiStringsToEmojis takes a slice of deduplicated, lowercase emojis in the form ":emojiname:", which have been
// used in a status. It takes the id of the account that wrote the status, and the id of the status itself, and then // used in a status. It takes the id of the account that wrote the status, and the id of the status itself, and then
@ -69,5 +72,5 @@ type DB interface {
// //
// Note: this func doesn't/shouldn't do any manipulation of the emoji in the DB, it's just for checking // Note: this func doesn't/shouldn't do any manipulation of the emoji in the DB, it's just for checking
// if they exist in the db and conveniently returning them if they do. // if they exist in the db and conveniently returning them if they do.
EmojiStringsToEmojis(emojis []string, originAccountID string, statusID string) ([]*gtsmodel.Emoji, error) EmojiStringsToEmojis(ctx context.Context, emojis []string, originAccountID string, statusID string) ([]*gtsmodel.Emoji, error)
} }

View file

@ -18,19 +18,22 @@
package db package db
import "net/url" import (
"context"
"net/url"
)
// Domain contains DB functions related to domains and domain blocks. // Domain contains DB functions related to domains and domain blocks.
type Domain interface { type Domain interface {
// IsDomainBlocked checks if an instance-level domain block exists for the given domain string (eg., `example.org`). // IsDomainBlocked checks if an instance-level domain block exists for the given domain string (eg., `example.org`).
IsDomainBlocked(domain string) (bool, Error) IsDomainBlocked(ctx context.Context, domain string) (bool, Error)
// AreDomainsBlocked checks if an instance-level domain block exists for any of the given domains strings, and returns true if even one is found. // AreDomainsBlocked checks if an instance-level domain block exists for any of the given domains strings, and returns true if even one is found.
AreDomainsBlocked(domains []string) (bool, Error) AreDomainsBlocked(ctx context.Context, domains []string) (bool, Error)
// IsURIBlocked checks if an instance-level domain block exists for the `host` in the given URI (eg., `https://example.org/users/whatever`). // IsURIBlocked checks if an instance-level domain block exists for the `host` in the given URI (eg., `https://example.org/users/whatever`).
IsURIBlocked(uri *url.URL) (bool, Error) IsURIBlocked(ctx context.Context, uri *url.URL) (bool, Error)
// AreURIsBlocked checks if an instance-level domain block exists for any `host` in the given URI slice, and returns true if even one is found. // AreURIsBlocked checks if an instance-level domain block exists for any `host` in the given URI slice, and returns true if even one is found.
AreURIsBlocked(uris []*url.URL) (bool, Error) AreURIsBlocked(ctx context.Context, uris []*url.URL) (bool, Error)
} }

View file

@ -18,19 +18,23 @@
package db package db
import "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" import (
"context"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
)
// Instance contains functions for instance-level actions (counting instance users etc.). // Instance contains functions for instance-level actions (counting instance users etc.).
type Instance interface { type Instance interface {
// CountInstanceUsers returns the number of known accounts registered with the given domain. // CountInstanceUsers returns the number of known accounts registered with the given domain.
CountInstanceUsers(domain string) (int, Error) CountInstanceUsers(ctx context.Context, domain string) (int, Error)
// CountInstanceStatuses returns the number of known statuses posted from the given domain. // CountInstanceStatuses returns the number of known statuses posted from the given domain.
CountInstanceStatuses(domain string) (int, Error) CountInstanceStatuses(ctx context.Context, domain string) (int, Error)
// CountInstanceDomains returns the number of known instances known that the given domain federates with. // CountInstanceDomains returns the number of known instances known that the given domain federates with.
CountInstanceDomains(domain string) (int, Error) CountInstanceDomains(ctx context.Context, domain string) (int, Error)
// GetInstanceAccounts returns a slice of accounts from the given instance, arranged by ID. // GetInstanceAccounts returns a slice of accounts from the given instance, arranged by ID.
GetInstanceAccounts(domain string, maxID string, limit int) ([]*gtsmodel.Account, Error) GetInstanceAccounts(ctx context.Context, domain string, maxID string, limit int) ([]*gtsmodel.Account, Error)
} }

View file

@ -18,10 +18,14 @@
package db package db
import "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" import (
"context"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
)
// Media contains functions related to creating/getting/removing media attachments. // Media contains functions related to creating/getting/removing media attachments.
type Media interface { type Media interface {
// GetAttachmentByID gets a single attachment by its ID // GetAttachmentByID gets a single attachment by its ID
GetAttachmentByID(id string) (*gtsmodel.MediaAttachment, Error) GetAttachmentByID(ctx context.Context, id string) (*gtsmodel.MediaAttachment, Error)
} }

View file

@ -18,13 +18,17 @@
package db package db
import "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" import (
"context"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
)
// Mention contains functions for getting/creating mentions in the database. // Mention contains functions for getting/creating mentions in the database.
type Mention interface { type Mention interface {
// GetMention gets a single mention by ID // GetMention gets a single mention by ID
GetMention(id string) (*gtsmodel.Mention, Error) GetMention(ctx context.Context, id string) (*gtsmodel.Mention, Error)
// GetMentions gets multiple mentions. // GetMentions gets multiple mentions.
GetMentions(ids []string) ([]*gtsmodel.Mention, Error) GetMentions(ctx context.Context, ids []string) ([]*gtsmodel.Mention, Error)
} }

View file

@ -18,14 +18,18 @@
package db package db
import "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" import (
"context"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
)
// Notification contains functions for creating and getting notifications. // Notification contains functions for creating and getting notifications.
type Notification interface { type Notification interface {
// GetNotifications returns a slice of notifications that pertain to the given accountID. // GetNotifications returns a slice of notifications that pertain to the given accountID.
// //
// Returned notifications will be ordered ID descending (ie., highest/newest to lowest/oldest). // Returned notifications will be ordered ID descending (ie., highest/newest to lowest/oldest).
GetNotifications(accountID string, limit int, maxID string, sinceID string) ([]*gtsmodel.Notification, Error) GetNotifications(ctx context.Context, accountID string, limit int, maxID string, sinceID string) ([]*gtsmodel.Notification, Error)
// GetNotification returns one notification according to its id. // GetNotification returns one notification according to its id.
GetNotification(id string) (*gtsmodel.Notification, Error) GetNotification(ctx context.Context, id string) (*gtsmodel.Notification, Error)
} }

View file

@ -1,205 +0,0 @@
/*
GoToSocial
Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package pg
import (
"context"
"errors"
"fmt"
"strings"
"github.com/go-pg/pg/v10"
"github.com/go-pg/pg/v10/orm"
"github.com/sirupsen/logrus"
"github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db"
)
type basicDB struct {
config *config.Config
conn *pg.DB
log *logrus.Logger
cancel context.CancelFunc
}
func (b *basicDB) Put(i interface{}) db.Error {
_, err := b.conn.Model(i).Insert(i)
if err != nil && strings.Contains(err.Error(), "duplicate key value violates unique constraint") {
return db.ErrAlreadyExists
}
return err
}
func (b *basicDB) GetByID(id string, i interface{}) db.Error {
if err := b.conn.Model(i).Where("id = ?", id).Select(); err != nil {
if err == pg.ErrNoRows {
return db.ErrNoEntries
}
return err
}
return nil
}
func (b *basicDB) GetWhere(where []db.Where, i interface{}) db.Error {
if len(where) == 0 {
return errors.New("no queries provided")
}
q := b.conn.Model(i)
for _, w := range where {
if w.Value == nil {
q = q.Where("? IS NULL", pg.Ident(w.Key))
} else {
if w.CaseInsensitive {
q = q.Where("LOWER(?) = LOWER(?)", pg.Safe(w.Key), w.Value)
} else {
q = q.Where("? = ?", pg.Safe(w.Key), w.Value)
}
}
}
if err := q.Select(); err != nil {
if err == pg.ErrNoRows {
return db.ErrNoEntries
}
return err
}
return nil
}
func (b *basicDB) GetAll(i interface{}) db.Error {
if err := b.conn.Model(i).Select(); err != nil {
if err == pg.ErrNoRows {
return db.ErrNoEntries
}
return err
}
return nil
}
func (b *basicDB) DeleteByID(id string, i interface{}) db.Error {
if _, err := b.conn.Model(i).Where("id = ?", id).Delete(); err != nil {
// if there are no rows *anyway* then that's fine
// just return err if there's an actual error
if err != pg.ErrNoRows {
return err
}
}
return nil
}
func (b *basicDB) DeleteWhere(where []db.Where, i interface{}) db.Error {
if len(where) == 0 {
return errors.New("no queries provided")
}
q := b.conn.Model(i)
for _, w := range where {
q = q.Where("? = ?", pg.Safe(w.Key), w.Value)
}
if _, err := q.Delete(); err != nil {
// if there are no rows *anyway* then that's fine
// just return err if there's an actual error
if err != pg.ErrNoRows {
return err
}
}
return nil
}
func (b *basicDB) Upsert(i interface{}, conflictColumn string) db.Error {
if _, err := b.conn.Model(i).OnConflict(fmt.Sprintf("(%s) DO UPDATE", conflictColumn)).Insert(); err != nil {
if err == pg.ErrNoRows {
return db.ErrNoEntries
}
return err
}
return nil
}
func (b *basicDB) UpdateByID(id string, i interface{}) db.Error {
if _, err := b.conn.Model(i).Where("id = ?", id).OnConflict("(id) DO UPDATE").Insert(); err != nil {
if err == pg.ErrNoRows {
return db.ErrNoEntries
}
return err
}
return nil
}
func (b *basicDB) UpdateOneByID(id string, key string, value interface{}, i interface{}) db.Error {
_, err := b.conn.Model(i).Set("? = ?", pg.Safe(key), value).Where("id = ?", id).Update()
return err
}
func (b *basicDB) UpdateWhere(where []db.Where, key string, value interface{}, i interface{}) db.Error {
q := b.conn.Model(i)
for _, w := range where {
if w.Value == nil {
q = q.Where("? IS NULL", pg.Ident(w.Key))
} else {
if w.CaseInsensitive {
q = q.Where("LOWER(?) = LOWER(?)", pg.Safe(w.Key), w.Value)
} else {
q = q.Where("? = ?", pg.Safe(w.Key), w.Value)
}
}
}
q = q.Set("? = ?", pg.Safe(key), value)
_, err := q.Update()
return err
}
func (b *basicDB) CreateTable(i interface{}) db.Error {
return b.conn.Model(i).CreateTable(&orm.CreateTableOptions{
IfNotExists: true,
})
}
func (b *basicDB) DropTable(i interface{}) db.Error {
return b.conn.Model(i).DropTable(&orm.DropTableOptions{
IfExists: true,
})
}
func (b *basicDB) RegisterTable(i interface{}) db.Error {
orm.RegisterTable(i)
return nil
}
func (b *basicDB) IsHealthy(ctx context.Context) db.Error {
return b.conn.Ping(ctx)
}
func (b *basicDB) Stop(ctx context.Context) db.Error {
b.log.Info("closing db connection")
if err := b.conn.Close(); err != nil {
// only cancel if there's a problem closing the db
b.cancel()
return err
}
return nil
}

View file

@ -1,318 +0,0 @@
/*
GoToSocial
Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package pg
import (
"container/list"
"context"
"errors"
"time"
"github.com/go-pg/pg/v10"
"github.com/go-pg/pg/v10/orm"
"github.com/sirupsen/logrus"
"github.com/superseriousbusiness/gotosocial/internal/cache"
"github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
)
type statusDB struct {
config *config.Config
conn *pg.DB
log *logrus.Logger
cancel context.CancelFunc
cache cache.Cache
}
func (s *statusDB) cacheStatus(id string, status *gtsmodel.Status) {
if s.cache == nil {
s.cache = cache.New()
}
if err := s.cache.Store(id, status); err != nil {
s.log.Panicf("statusDB: error storing in cache: %s", err)
}
}
func (s *statusDB) statusCached(id string) (*gtsmodel.Status, bool) {
if s.cache == nil {
s.cache = cache.New()
return nil, false
}
sI, err := s.cache.Fetch(id)
if err != nil || sI == nil {
return nil, false
}
status, ok := sI.(*gtsmodel.Status)
if !ok {
s.log.Panicf("statusDB: cached interface with key %s was not a status", id)
}
return status, true
}
func (s *statusDB) newStatusQ(status interface{}) *orm.Query {
return s.conn.Model(status).
Relation("Attachments").
Relation("Tags").
Relation("Mentions").
Relation("Emojis").
Relation("Account").
Relation("InReplyTo").
Relation("InReplyToAccount").
Relation("BoostOf").
Relation("BoostOfAccount").
Relation("CreatedWithApplication")
}
func (s *statusDB) newFaveQ(faves interface{}) *orm.Query {
return s.conn.Model(faves).
Relation("Account").
Relation("TargetAccount").
Relation("Status")
}
func (s *statusDB) GetStatusByID(id string) (*gtsmodel.Status, db.Error) {
if status, cached := s.statusCached(id); cached {
return status, nil
}
status := &gtsmodel.Status{}
q := s.newStatusQ(status).
Where("status.id = ?", id)
err := processErrorResponse(q.Select())
if err == nil && status != nil {
s.cacheStatus(id, status)
}
return status, err
}
func (s *statusDB) GetStatusByURI(uri string) (*gtsmodel.Status, db.Error) {
if status, cached := s.statusCached(uri); cached {
return status, nil
}
status := &gtsmodel.Status{}
q := s.newStatusQ(status).
Where("LOWER(status.uri) = LOWER(?)", uri)
err := processErrorResponse(q.Select())
if err == nil && status != nil {
s.cacheStatus(uri, status)
}
return status, err
}
func (s *statusDB) GetStatusByURL(uri string) (*gtsmodel.Status, db.Error) {
if status, cached := s.statusCached(uri); cached {
return status, nil
}
status := &gtsmodel.Status{}
q := s.newStatusQ(status).
Where("LOWER(status.url) = LOWER(?)", uri)
err := processErrorResponse(q.Select())
if err == nil && status != nil {
s.cacheStatus(uri, status)
}
return status, err
}
func (s *statusDB) PutStatus(status *gtsmodel.Status) db.Error {
transaction := func(tx *pg.Tx) error {
// create links between this status and any emojis it uses
for _, i := range status.EmojiIDs {
if _, err := tx.Model(&gtsmodel.StatusToEmoji{
StatusID: status.ID,
EmojiID: i,
}).Insert(); err != nil {
return err
}
}
// create links between this status and any tags it uses
for _, i := range status.TagIDs {
if _, err := tx.Model(&gtsmodel.StatusToTag{
StatusID: status.ID,
TagID: i,
}).Insert(); err != nil {
return err
}
}
// change the status ID of the media attachments to the new status
for _, a := range status.Attachments {
a.StatusID = status.ID
a.UpdatedAt = time.Now()
if _, err := s.conn.Model(a).
Where("id = ?", a.ID).
Update(); err != nil {
return err
}
}
_, err := tx.Model(status).Insert()
return err
}
return processErrorResponse(s.conn.RunInTransaction(context.Background(), transaction))
}
func (s *statusDB) GetStatusParents(status *gtsmodel.Status, onlyDirect bool) ([]*gtsmodel.Status, db.Error) {
parents := []*gtsmodel.Status{}
s.statusParent(status, &parents, onlyDirect)
return parents, nil
}
func (s *statusDB) statusParent(status *gtsmodel.Status, foundStatuses *[]*gtsmodel.Status, onlyDirect bool) {
if status.InReplyToID == "" {
return
}
parentStatus, err := s.GetStatusByID(status.InReplyToID)
if err == nil {
*foundStatuses = append(*foundStatuses, parentStatus)
}
if onlyDirect {
return
}
s.statusParent(parentStatus, foundStatuses, false)
}
func (s *statusDB) GetStatusChildren(status *gtsmodel.Status, onlyDirect bool, minID string) ([]*gtsmodel.Status, db.Error) {
foundStatuses := &list.List{}
foundStatuses.PushFront(status)
s.statusChildren(status, foundStatuses, onlyDirect, minID)
children := []*gtsmodel.Status{}
for e := foundStatuses.Front(); e != nil; e = e.Next() {
entry, ok := e.Value.(*gtsmodel.Status)
if !ok {
panic(errors.New("entry in foundStatuses was not a *gtsmodel.Status"))
}
// only append children, not the overall parent status
if entry.ID != status.ID {
children = append(children, entry)
}
}
return children, nil
}
func (s *statusDB) statusChildren(status *gtsmodel.Status, foundStatuses *list.List, onlyDirect bool, minID string) {
immediateChildren := []*gtsmodel.Status{}
q := s.conn.Model(&immediateChildren).Where("in_reply_to_id = ?", status.ID)
if minID != "" {
q = q.Where("status.id > ?", minID)
}
if err := q.Select(); err != nil {
return
}
for _, child := range immediateChildren {
insertLoop:
for e := foundStatuses.Front(); e != nil; e = e.Next() {
entry, ok := e.Value.(*gtsmodel.Status)
if !ok {
panic(errors.New("entry in foundStatuses was not a *gtsmodel.Status"))
}
if child.InReplyToAccountID != "" && entry.ID == child.InReplyToID {
foundStatuses.InsertAfter(child, e)
break insertLoop
}
}
// only do one loop if we only want direct children
if onlyDirect {
return
}
s.statusChildren(child, foundStatuses, false, minID)
}
}
func (s *statusDB) CountStatusReplies(status *gtsmodel.Status) (int, db.Error) {
return s.conn.Model(&gtsmodel.Status{}).Where("in_reply_to_id = ?", status.ID).Count()
}
func (s *statusDB) CountStatusReblogs(status *gtsmodel.Status) (int, db.Error) {
return s.conn.Model(&gtsmodel.Status{}).Where("boost_of_id = ?", status.ID).Count()
}
func (s *statusDB) CountStatusFaves(status *gtsmodel.Status) (int, db.Error) {
return s.conn.Model(&gtsmodel.StatusFave{}).Where("status_id = ?", status.ID).Count()
}
func (s *statusDB) IsStatusFavedBy(status *gtsmodel.Status, accountID string) (bool, db.Error) {
return s.conn.Model(&gtsmodel.StatusFave{}).Where("status_id = ?", status.ID).Where("account_id = ?", accountID).Exists()
}
func (s *statusDB) IsStatusRebloggedBy(status *gtsmodel.Status, accountID string) (bool, db.Error) {
return s.conn.Model(&gtsmodel.Status{}).Where("boost_of_id = ?", status.ID).Where("account_id = ?", accountID).Exists()
}
func (s *statusDB) IsStatusMutedBy(status *gtsmodel.Status, accountID string) (bool, db.Error) {
return s.conn.Model(&gtsmodel.StatusMute{}).Where("status_id = ?", status.ID).Where("account_id = ?", accountID).Exists()
}
func (s *statusDB) IsStatusBookmarkedBy(status *gtsmodel.Status, accountID string) (bool, db.Error) {
return s.conn.Model(&gtsmodel.StatusBookmark{}).Where("status_id = ?", status.ID).Where("account_id = ?", accountID).Exists()
}
func (s *statusDB) GetStatusFaves(status *gtsmodel.Status) ([]*gtsmodel.StatusFave, db.Error) {
faves := []*gtsmodel.StatusFave{}
q := s.newFaveQ(&faves).
Where("status_id = ?", status.ID)
err := processErrorResponse(q.Select())
return faves, err
}
func (s *statusDB) GetStatusReblogs(status *gtsmodel.Status) ([]*gtsmodel.Status, db.Error) {
reblogs := []*gtsmodel.Status{}
q := s.newStatusQ(&reblogs).
Where("boost_of_id = ?", status.ID)
err := processErrorResponse(q.Select())
return reblogs, err
}

View file

@ -1,25 +0,0 @@
package pg
import (
"strings"
"github.com/go-pg/pg/v10"
"github.com/superseriousbusiness/gotosocial/internal/db"
)
// processErrorResponse parses the given error and returns an appropriate DBError.
func processErrorResponse(err error) db.Error {
switch err {
case nil:
return nil
case pg.ErrNoRows:
return db.ErrNoEntries
case pg.ErrMultiRows:
return db.ErrMultipleEntries
default:
if strings.Contains(err.Error(), "duplicate key value violates unique constraint") {
return db.ErrAlreadyExists
}
return err
}
}

View file

@ -18,54 +18,58 @@
package db package db
import "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" import (
"context"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
)
// Relationship contains functions for getting or modifying the relationship between two accounts. // Relationship contains functions for getting or modifying the relationship between two accounts.
type Relationship interface { type Relationship interface {
// IsBlocked checks whether account 1 has a block in place against block2. // IsBlocked checks whether account 1 has a block in place against block2.
// If eitherDirection is true, then the function returns true if account1 blocks account2, OR if account2 blocks account1. // If eitherDirection is true, then the function returns true if account1 blocks account2, OR if account2 blocks account1.
IsBlocked(account1 string, account2 string, eitherDirection bool) (bool, Error) IsBlocked(ctx context.Context, account1 string, account2 string, eitherDirection bool) (bool, Error)
// GetBlock returns the block from account1 targeting account2, if it exists, or an error if it doesn't. // GetBlock returns the block from account1 targeting account2, if it exists, or an error if it doesn't.
// //
// Because this is slower than Blocked, only use it if you need the actual Block struct for some reason, // Because this is slower than Blocked, only use it if you need the actual Block struct for some reason,
// not if you're just checking for the existence of a block. // not if you're just checking for the existence of a block.
GetBlock(account1 string, account2 string) (*gtsmodel.Block, Error) GetBlock(ctx context.Context, account1 string, account2 string) (*gtsmodel.Block, Error)
// GetRelationship retrieves the relationship of the targetAccount to the requestingAccount. // GetRelationship retrieves the relationship of the targetAccount to the requestingAccount.
GetRelationship(requestingAccount string, targetAccount string) (*gtsmodel.Relationship, Error) GetRelationship(ctx context.Context, requestingAccount string, targetAccount string) (*gtsmodel.Relationship, Error)
// IsFollowing returns true if sourceAccount follows target account, or an error if something goes wrong while finding out. // IsFollowing returns true if sourceAccount follows target account, or an error if something goes wrong while finding out.
IsFollowing(sourceAccount *gtsmodel.Account, targetAccount *gtsmodel.Account) (bool, Error) IsFollowing(ctx context.Context, sourceAccount *gtsmodel.Account, targetAccount *gtsmodel.Account) (bool, Error)
// IsFollowRequested returns true if sourceAccount has requested to follow target account, or an error if something goes wrong while finding out. // IsFollowRequested returns true if sourceAccount has requested to follow target account, or an error if something goes wrong while finding out.
IsFollowRequested(sourceAccount *gtsmodel.Account, targetAccount *gtsmodel.Account) (bool, Error) IsFollowRequested(ctx context.Context, sourceAccount *gtsmodel.Account, targetAccount *gtsmodel.Account) (bool, Error)
// IsMutualFollowing returns true if account1 and account2 both follow each other, or an error if something goes wrong while finding out. // IsMutualFollowing returns true if account1 and account2 both follow each other, or an error if something goes wrong while finding out.
IsMutualFollowing(account1 *gtsmodel.Account, account2 *gtsmodel.Account) (bool, Error) IsMutualFollowing(ctx context.Context, account1 *gtsmodel.Account, account2 *gtsmodel.Account) (bool, Error)
// AcceptFollowRequest moves a follow request in the database from the follow_requests table to the follows table. // AcceptFollowRequest moves a follow request in the database from the follow_requests table to the follows table.
// In other words, it should create the follow, and delete the existing follow request. // In other words, it should create the follow, and delete the existing follow request.
// //
// It will return the newly created follow for further processing. // It will return the newly created follow for further processing.
AcceptFollowRequest(originAccountID string, targetAccountID string) (*gtsmodel.Follow, Error) AcceptFollowRequest(ctx context.Context, originAccountID string, targetAccountID string) (*gtsmodel.Follow, Error)
// GetAccountFollowRequests returns all follow requests targeting the given account. // GetAccountFollowRequests returns all follow requests targeting the given account.
GetAccountFollowRequests(accountID string) ([]*gtsmodel.FollowRequest, Error) GetAccountFollowRequests(ctx context.Context, accountID string) ([]*gtsmodel.FollowRequest, Error)
// GetAccountFollows returns a slice of follows owned by the given accountID. // GetAccountFollows returns a slice of follows owned by the given accountID.
GetAccountFollows(accountID string) ([]*gtsmodel.Follow, Error) GetAccountFollows(ctx context.Context, accountID string) ([]*gtsmodel.Follow, Error)
// CountAccountFollows returns the amount of accounts that the given accountID is following. // CountAccountFollows returns the amount of accounts that the given accountID is following.
// //
// If localOnly is set to true, then only follows from *this instance* will be returned. // If localOnly is set to true, then only follows from *this instance* will be returned.
CountAccountFollows(accountID string, localOnly bool) (int, Error) CountAccountFollows(ctx context.Context, accountID string, localOnly bool) (int, Error)
// GetAccountFollowedBy fetches follows that target given accountID. // GetAccountFollowedBy fetches follows that target given accountID.
// //
// If localOnly is set to true, then only follows from *this instance* will be returned. // If localOnly is set to true, then only follows from *this instance* will be returned.
GetAccountFollowedBy(accountID string, localOnly bool) ([]*gtsmodel.Follow, Error) GetAccountFollowedBy(ctx context.Context, accountID string, localOnly bool) ([]*gtsmodel.Follow, Error)
// CountAccountFollowedBy returns the amounts that the given ID is followed by. // CountAccountFollowedBy returns the amounts that the given ID is followed by.
CountAccountFollowedBy(accountID string, localOnly bool) (int, Error) CountAccountFollowedBy(ctx context.Context, accountID string, localOnly bool) (int, Error)
} }

View file

@ -16,26 +16,16 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package dereferencing package db
import ( import (
"github.com/superseriousbusiness/gotosocial/internal/db" "context"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
) )
func (d *deref) blockedDomain(host string) (bool, error) { // Session handles getting/creation of router sessions.
b := &gtsmodel.DomainBlock{} type Session interface {
err := d.db.GetWhere([]db.Where{{Key: "domain", Value: host, CaseInsensitive: true}}, b) GetSession(ctx context.Context) (*gtsmodel.RouterSession, Error)
if err == nil { CreateSession(ctx context.Context) (*gtsmodel.RouterSession, Error)
// block exists
return true, nil
}
if err == db.ErrNoEntries {
// there are no entries so there's no block
return false, nil
}
// there's an actual error
return false, err
} }

Some files were not shown because too many files have changed in this diff Show more