This commit is contained in:
tsmethurst 2021-08-24 16:54:54 +02:00
commit 526a14a92d
486 changed files with 84353 additions and 23865 deletions

5
go.mod
View file

@ -21,14 +21,15 @@ require (
github.com/go-errors/errors v1.4.0 // indirect
github.com/go-fed/activity v1.0.1-0.20210803212804-d866ba75dd0f
github.com/go-fed/httpsig v1.1.0
github.com/go-pg/pg/v10 v10.10.3
github.com/go-playground/validator/v10 v10.7.0 // indirect
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 // 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/gorilla/sessions v1.2.1 // indirect
github.com/gorilla/websocket v1.4.2
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/leodido/go-urn v1.2.1 // indirect
github.com/mattn/go-isatty v0.0.13 // indirect
@ -37,7 +38,6 @@ require (
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
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/russross/blackfriday/v2 v2.1.0
github.com/sirupsen/logrus v1.8.1
@ -48,7 +48,6 @@ require (
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/uptrace/bun/driver/pgdriver v0.4.3
github.com/urfave/cli/v2 v2.3.0
github.com/wagslane/go-password-validator v0.3.0
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97

153
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=
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/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/go.mod h1:8BGGzdumrIjWxdRx8zpK6L3oGMWvIXdvB2GD1cfvd+I=
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/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/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/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.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU=
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/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=
@ -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/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.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
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/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc=
@ -131,10 +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/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-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-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
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/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM=
@ -146,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.7.0 h1:gLi5ajTBBheLNt0ctewgq7eolXoDALQd5/y90Hh9ZgM=
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/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/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM=
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/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-20200319012246-673a6f80352d/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 h1:gtexQ/VGyN+VVFRXSFiguSNcXmS6rkKT+X7FdIrTtfo=
@ -183,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.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
@ -239,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/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/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/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
@ -256,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.10 h1:a/y8CglcM7gLGYmlbP/stPE5sR3hbhFRUjCBfd/0B3I=
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.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
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.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
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/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.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
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/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-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.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA=
@ -285,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/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/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.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/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
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.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.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/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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
@ -311,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/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/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.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
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/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/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/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/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
@ -326,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/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.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.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
@ -382,8 +450,6 @@ 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/uptrace/bun/driver/pgdriver v0.4.3 h1:WLtUL3xtnZuryRcXII8PV8dm6UfEMWQniHFmV5T4tEw=
github.com/uptrace/bun/driver/pgdriver v0.4.3/go.mod h1:CQsGmzHK9Sq70avzRy7aFYAomoT3XihjGPtRDSToV+0=
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/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
@ -391,13 +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/go.mod h1:ol1PCaL0dX20wC0htZ7sYCsvCYmrouYra0zHzaclZhE=
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/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/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc=
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/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
github.com/wagslane/go-password-validator v0.3.0 h1:vfxOPzGHkz5S146HDpavl0cw1DSVP061Ry2PX0/ON6I=
@ -418,23 +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.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/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.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.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/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-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-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-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-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
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/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@ -481,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-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-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-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=
@ -498,11 +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-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-20201006153459-a7d1128ccaa0/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-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/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@ -520,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-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-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-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-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-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-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -557,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-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-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-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-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/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/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -575,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.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.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
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/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@ -588,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-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-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-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-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-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-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
@ -603,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-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-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-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
@ -621,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-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-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
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-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -709,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 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-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/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/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/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/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/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
@ -739,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-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=
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/go.mod h1:AjuTy7gEiUArFMjgBBDU4SMxlfUYsRokpJQgNWOt3e4=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=

View file

@ -120,7 +120,7 @@ var Start cliactions.GTSAction = func(ctx context.Context, c *config.Config, log
transportController := transport.NewController(c, dbService, &federation.Clock{}, http.DefaultClient, log)
federator := federation.NewFederator(dbService, federatingDB, transportController, c, log, typeConverter, mediaHandler)
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)
}

View file

@ -63,7 +63,7 @@ var Start cliactions.GTSAction = func(ctx context.Context, _ *config.Config, log
federator := testrig.NewTestFederator(dbService, transportController, storageBackend)
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)
}

View file

@ -250,7 +250,7 @@ func (a *adminDB) CreateInstanceAccount(ctx context.Context) db.Error {
return err
}
a.log.Infof("instance account CREATED with id %s", username, acct.ID)
a.log.Infof("instance account %s CREATED with id %s", username, acct.ID)
return nil
}

View file

@ -175,7 +175,7 @@ func (b *basicDB) CreateTable(ctx context.Context, i interface{}) db.Error {
}
func (b *basicDB) DropTable(ctx context.Context, i interface{}) db.Error {
_, err := b.conn.NewDropTable().Model(i).Exec(ctx)
_, err := b.conn.NewDropTable().IfExists().Model(i).Exec(ctx)
return processErrorResponse(err)
}

View file

@ -42,21 +42,13 @@ func (d *domainDB) IsDomainBlocked(ctx context.Context, domain string) (bool, db
return false, nil
}
count, err := d.conn.
q := d.conn.
NewSelect().
Model(&gtsmodel.DomainBlock{}).
Where("LOWER(domain) = LOWER(?)", domain).
Limit(1).
Count(ctx)
Limit(1)
blocked := count != 0
err = processErrorResponse(err)
if err != db.ErrNoEntries {
return false, err
}
return blocked, nil
return exists(ctx, q)
}
func (d *domainDB) AreDomainsBlocked(ctx context.Context, domains []string) (bool, db.Error) {

View file

@ -30,6 +30,8 @@ import (
"strings"
"time"
"github.com/jackc/pgx/v4"
"github.com/jackc/pgx/v4/stdlib"
"github.com/sirupsen/logrus"
"github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db"
@ -37,7 +39,6 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/id"
"github.com/uptrace/bun"
"github.com/uptrace/bun/dialect/pgdialect"
"github.com/uptrace/bun/driver/pgdriver"
)
var registerTables []interface{} = []interface{}{
@ -67,19 +68,13 @@ type postgresService struct {
// NewPostgresService returns a postgresService 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.
func NewPostgresService(ctx context.Context, c *config.Config, log *logrus.Logger) (db.DB, error) {
for _, t := range registerTables {
// https://pg.uptrace.dev/orm/many-to-many-relation/
bun.RegisterModel(t)
}
opts, err := derivePGOptions(c)
if err != nil {
return nil, fmt.Errorf("could not create postgres service: %s", err)
return nil, fmt.Errorf("could not create postgres options: %s", err)
}
log.Debugf("using pg options: %+v", opts)
sqldb := sql.OpenDB(pgdriver.NewConnector(opts...))
log.Debugf("using opts %+v", opts)
sqldb := stdlib.OpenDB(*opts)
conn := bun.NewDB(sqldb, pgdialect.New())
// actually *begin* the connection so that we can tell if the db is there and listening
@ -88,6 +83,12 @@ func NewPostgresService(ctx context.Context, c *config.Config, log *logrus.Logge
}
log.Info("connected to postgres")
for _, t := range registerTables {
// https://bun.uptrace.dev/orm/many-to-many-relation/
conn.RegisterModel(t)
}
log.Info("models registered")
ps := &postgresService{
Account: &accountDB{
config: c,
@ -157,9 +158,9 @@ func NewPostgresService(ctx context.Context, c *config.Config, log *logrus.Logge
HANDY STUFF
*/
// derivePGOptions takes an application config and returns either a ready-to-use *pg.Options
// derivePGOptions 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.
func derivePGOptions(c *config.Config) ([]pgdriver.DriverOption, error) {
func derivePGOptions(c *config.Config) (*pgx.ConnConfig, error) {
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)
}
@ -237,18 +238,15 @@ func derivePGOptions(c *config.Config) ([]pgdriver.DriverOption, error) {
tlsConfig.RootCAs = certPool
}
// We can rely on the pg library we're using to set
// sensible defaults for everything we don't set here.
options := []pgdriver.DriverOption{
pgdriver.WithAddr(fmt.Sprintf("%s:%d", c.DBConfig.Address, c.DBConfig.Port)),
pgdriver.WithUser(c.DBConfig.User),
pgdriver.WithPassword(c.DBConfig.Password),
pgdriver.WithDatabase(c.DBConfig.Database),
pgdriver.WithApplicationName(c.ApplicationName),
pgdriver.WithTLSConfig(tlsConfig),
}
opts, _ := pgx.ParseConfig("")
opts.Host = c.DBConfig.Address
opts.Port = uint16(c.DBConfig.Port)
opts.User = c.DBConfig.User
opts.Password = c.DBConfig.Password
opts.TLSConfig = tlsConfig
opts.PreferSimpleProtocol = true
return options, nil
return opts, nil
}
/*
@ -257,9 +255,9 @@ func derivePGOptions(c *config.Config) ([]pgdriver.DriverOption, error) {
// 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 *postgresService) MentionStringsToMentions(ctx context.Context, targetAccounts []string, originAccountID string, statusID string) ([]*gtsmodel.Mention, error) {
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
}
@ -304,14 +302,14 @@ func (ps *postgresService) MentionStringsToMentions(targetAccounts []string, ori
// match username + account, case insensitive
if local {
// 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 {
// 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 == 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
ps.log.Debugf("no account found with username '%s' and domain '%s', skipping it", username, domain)
continue
@ -335,14 +333,14 @@ func (ps *postgresService) MentionStringsToMentions(targetAccounts []string, ori
return menchies, nil
}
func (ps *postgresService) TagStringsToTags(tags []string, originAccountID string, statusID string) ([]*gtsmodel.Tag, error) {
func (ps *postgresService) TagStringsToTags(ctx context.Context, tags []string, originAccountID string, statusID string) ([]*gtsmodel.Tag, error) {
newTags := []*gtsmodel.Tag{}
for _, t := range tags {
tag := &gtsmodel.Tag{}
// 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
if err := ps.conn.Model(tag).Where("LOWER(?) = LOWER(?)", pg.Ident("name"), t).Select(); err != nil {
if err == pg.ErrNoRows {
if err := ps.conn.NewSelect().Model(tag).Where("LOWER(?) = LOWER(?)", bun.Ident("name"), t).Scan(ctx); err != nil {
if err == sql.ErrNoRows {
// tag doesn't exist yet so populate it
newID, err := id.NewRandomULID()
if err != nil {
@ -371,13 +369,13 @@ func (ps *postgresService) TagStringsToTags(tags []string, originAccountID strin
return newTags, nil
}
func (ps *postgresService) EmojiStringsToEmojis(emojis []string, originAccountID string, statusID string) ([]*gtsmodel.Emoji, error) {
func (ps *postgresService) EmojiStringsToEmojis(ctx context.Context, emojis []string, originAccountID string, statusID string) ([]*gtsmodel.Emoji, error) {
newEmojis := []*gtsmodel.Emoji{}
for _, e := range emojis {
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 == 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
ps.log.Debugf("no emoji found with shortcode %s, skipping it", e)
continue

View file

@ -67,16 +67,7 @@ func (r *relationshipDB) IsBlocked(ctx context.Context, account1 string, account
Where("account_id = ?", account2)
}
count, err := q.Count(ctx)
blocked := count != 0
err = processErrorResponse(err)
if err != db.ErrNoEntries {
return false, err
}
return blocked, nil
return exists(ctx, q)
}
func (r *relationshipDB) GetBlock(ctx context.Context, account1 string, account2 string) (*gtsmodel.Block, db.Error) {
@ -186,16 +177,7 @@ func (r *relationshipDB) IsFollowing(ctx context.Context, sourceAccount *gtsmode
Where("target_account_id = ?", targetAccount.ID).
Limit(1)
count, err := q.Count(ctx)
following := count != 0
err = processErrorResponse(err)
if err != db.ErrNoEntries {
return false, err
}
return following, nil
return exists(ctx, q)
}
func (r *relationshipDB) IsFollowRequested(ctx context.Context, sourceAccount *gtsmodel.Account, targetAccount *gtsmodel.Account) (bool, db.Error) {
@ -209,16 +191,7 @@ func (r *relationshipDB) IsFollowRequested(ctx context.Context, sourceAccount *g
Where("account_id = ?", sourceAccount.ID).
Where("target_account_id = ?", targetAccount.ID)
count, err := q.Count(ctx)
followRequested := count != 0
err = processErrorResponse(err)
if err != db.ErrNoEntries {
return false, err
}
return followRequested, nil
return exists(ctx, q)
}
func (r *relationshipDB) IsMutualFollowing(ctx context.Context, account1 *gtsmodel.Account, account2 *gtsmodel.Account) (bool, db.Error) {

View file

@ -24,8 +24,6 @@ import (
"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"
@ -71,8 +69,10 @@ func (s *statusDB) statusCached(id string) (*gtsmodel.Status, bool) {
return status, true
}
func (s *statusDB) newStatusQ(status interface{}) *orm.Query {
return s.conn.Model(status).
func (s *statusDB) newStatusQ(status interface{}) *bun.SelectQuery {
return s.conn.
NewSelect().
Model(status).
Relation("Attachments").
Relation("Tags").
Relation("Mentions").
@ -85,14 +85,16 @@ func (s *statusDB) newStatusQ(status interface{}) *orm.Query {
Relation("CreatedWithApplication")
}
func (s *statusDB) newFaveQ(faves interface{}) *orm.Query {
return s.conn.Model(faves).
func (s *statusDB) newFaveQ(faves interface{}) *bun.SelectQuery {
return s.conn.
NewSelect().
Model(faves).
Relation("Account").
Relation("TargetAccount").
Relation("Status")
}
func (s *statusDB) GetStatusByID(id string) (*gtsmodel.Status, db.Error) {
func (s *statusDB) GetStatusByID(ctx context.Context, id string) (*gtsmodel.Status, db.Error) {
if status, cached := s.statusCached(id); cached {
return status, nil
}
@ -102,7 +104,7 @@ func (s *statusDB) GetStatusByID(id string) (*gtsmodel.Status, db.Error) {
q := s.newStatusQ(status).
Where("status.id = ?", id)
err := processErrorResponse(q.Select())
err := processErrorResponse(q.Scan(ctx))
if err == nil && status != nil {
s.cacheStatus(id, status)
@ -111,7 +113,7 @@ func (s *statusDB) GetStatusByID(id string) (*gtsmodel.Status, db.Error) {
return status, err
}
func (s *statusDB) GetStatusByURI(uri string) (*gtsmodel.Status, db.Error) {
func (s *statusDB) GetStatusByURI(ctx context.Context, uri string) (*gtsmodel.Status, db.Error) {
if status, cached := s.statusCached(uri); cached {
return status, nil
}
@ -121,7 +123,7 @@ func (s *statusDB) GetStatusByURI(uri string) (*gtsmodel.Status, db.Error) {
q := s.newStatusQ(status).
Where("LOWER(status.uri) = LOWER(?)", uri)
err := processErrorResponse(q.Select())
err := processErrorResponse(q.Scan(ctx))
if err == nil && status != nil {
s.cacheStatus(uri, status)
@ -130,7 +132,7 @@ func (s *statusDB) GetStatusByURI(uri string) (*gtsmodel.Status, db.Error) {
return status, err
}
func (s *statusDB) GetStatusByURL(uri string) (*gtsmodel.Status, db.Error) {
func (s *statusDB) GetStatusByURL(ctx context.Context, uri string) (*gtsmodel.Status, db.Error) {
if status, cached := s.statusCached(uri); cached {
return status, nil
}
@ -140,7 +142,7 @@ func (s *statusDB) GetStatusByURL(uri string) (*gtsmodel.Status, db.Error) {
q := s.newStatusQ(status).
Where("LOWER(status.url) = LOWER(?)", uri)
err := processErrorResponse(q.Select())
err := processErrorResponse(q.Scan(ctx))
if err == nil && status != nil {
s.cacheStatus(uri, status)
@ -149,24 +151,24 @@ func (s *statusDB) GetStatusByURL(uri string) (*gtsmodel.Status, db.Error) {
return status, err
}
func (s *statusDB) PutStatus(status *gtsmodel.Status) db.Error {
transaction := func(tx *pg.Tx) error {
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.Model(&gtsmodel.StatusToEmoji{
if _, err := tx.NewInsert().Model(&gtsmodel.StatusToEmoji{
StatusID: status.ID,
EmojiID: i,
}).Insert(); err != nil {
}).Exec(ctx); 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{
if _, err := tx.NewInsert().Model(&gtsmodel.StatusToTag{
StatusID: status.ID,
TagID: i,
}).Insert(); err != nil {
}).Exec(ctx); err != nil {
return err
}
}
@ -175,33 +177,33 @@ func (s *statusDB) PutStatus(status *gtsmodel.Status) db.Error {
for _, a := range status.Attachments {
a.StatusID = status.ID
a.UpdatedAt = time.Now()
if _, err := s.conn.Model(a).
if _, err := s.conn.NewUpdate().Model(a).
Where("id = ?", a.ID).
Update(); err != nil {
Exec(ctx); err != nil {
return err
}
}
_, err := tx.Model(status).Insert()
_, err := tx.NewInsert().Model(status).Exec(ctx)
return err
}
return processErrorResponse(s.conn.RunInTransaction(context.Background(), transaction))
return processErrorResponse(s.conn.RunInTx(ctx, nil, transaction))
}
func (s *statusDB) GetStatusParents(status *gtsmodel.Status, onlyDirect bool) ([]*gtsmodel.Status, db.Error) {
func (s *statusDB) GetStatusParents(ctx context.Context, status *gtsmodel.Status, onlyDirect bool) ([]*gtsmodel.Status, db.Error) {
parents := []*gtsmodel.Status{}
s.statusParent(status, &parents, onlyDirect)
s.statusParent(ctx, status, &parents, onlyDirect)
return parents, nil
}
func (s *statusDB) statusParent(status *gtsmodel.Status, foundStatuses *[]*gtsmodel.Status, onlyDirect bool) {
func (s *statusDB) statusParent(ctx context.Context, status *gtsmodel.Status, foundStatuses *[]*gtsmodel.Status, onlyDirect bool) {
if status.InReplyToID == "" {
return
}
parentStatus, err := s.GetStatusByID(status.InReplyToID)
parentStatus, err := s.GetStatusByID(ctx, status.InReplyToID)
if err == nil {
*foundStatuses = append(*foundStatuses, parentStatus)
}
@ -210,13 +212,13 @@ func (s *statusDB) statusParent(status *gtsmodel.Status, foundStatuses *[]*gtsmo
return
}
s.statusParent(parentStatus, foundStatuses, false)
s.statusParent(ctx, parentStatus, foundStatuses, false)
}
func (s *statusDB) GetStatusChildren(status *gtsmodel.Status, onlyDirect bool, minID string) ([]*gtsmodel.Status, db.Error) {
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(status, foundStatuses, onlyDirect, minID)
s.statusChildren(ctx, status, foundStatuses, onlyDirect, minID)
children := []*gtsmodel.Status{}
for e := foundStatuses.Front(); e != nil; e = e.Next() {
@ -234,15 +236,18 @@ func (s *statusDB) GetStatusChildren(status *gtsmodel.Status, onlyDirect bool, m
return children, nil
}
func (s *statusDB) statusChildren(status *gtsmodel.Status, foundStatuses *list.List, onlyDirect bool, minID string) {
func (s *statusDB) statusChildren(ctx context.Context, 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)
q := s.conn.
NewSelect().
Model(&immediateChildren).
Where("in_reply_to_id = ?", status.ID)
if minID != "" {
q = q.Where("status.id > ?", minID)
}
if err := q.Select(); err != nil {
if err := q.Scan(ctx); err != nil {
return
}
@ -264,56 +269,78 @@ func (s *statusDB) statusChildren(status *gtsmodel.Status, foundStatuses *list.L
if onlyDirect {
return
}
s.statusChildren(child, foundStatuses, false, minID)
s.statusChildren(ctx, 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) 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(status *gtsmodel.Status) (int, db.Error) {
return s.conn.Model(&gtsmodel.Status{}).Where("boost_of_id = ?", status.ID).Count()
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(status *gtsmodel.Status) (int, db.Error) {
return s.conn.Model(&gtsmodel.StatusFave{}).Where("status_id = ?", status.ID).Count()
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(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) 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(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) 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(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) 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(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) 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(status *gtsmodel.Status) ([]*gtsmodel.StatusFave, db.Error) {
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.Select())
err := processErrorResponse(q.Scan(ctx))
return faves, err
}
func (s *statusDB) GetStatusReblogs(status *gtsmodel.Status) ([]*gtsmodel.Status, db.Error) {
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.Select())
err := processErrorResponse(q.Scan(ctx))
return reblogs, err
}

View file

@ -19,6 +19,7 @@
package pg_test
import (
"context"
"fmt"
"testing"
"time"
@ -56,7 +57,7 @@ func (suite *StatusTestSuite) TearDownTest() {
}
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 {
suite.FailNow(err.Error())
}
@ -67,10 +68,11 @@ func (suite *StatusTestSuite) TestGetStatusByID() {
suite.Nil(status.BoostOfAccount)
suite.Nil(status.InReplyTo)
suite.Nil(status.InReplyToAccount)
suite.log.Debug("test finished")
}
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 {
suite.FailNow(err.Error())
}
@ -84,7 +86,7 @@ func (suite *StatusTestSuite) TestGetStatusByURI() {
}
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 {
suite.FailNow(err.Error())
}
@ -97,7 +99,7 @@ func (suite *StatusTestSuite) TestGetStatusWithExtras() {
}
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 {
suite.FailNow(err.Error())
}
@ -112,14 +114,14 @@ func (suite *StatusTestSuite) TestGetStatusWithMention() {
func (suite *StatusTestSuite) TestGetStatusTwice() {
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)
after1 := time.Now()
duration1 := after1.Sub(before1)
fmt.Println(duration1.Nanoseconds())
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)
after2 := time.Now()
duration2 := after2.Sub(before2)

View file

@ -20,9 +20,9 @@ package pg
import (
"context"
"database/sql"
"sort"
"github.com/go-pg/pg/v10"
"github.com/sirupsen/logrus"
"github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db"
@ -37,23 +37,27 @@ type timelineDB struct {
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{}
q := t.conn.Model(&statuses)
q := t.conn.
NewSelect().
Model(&statuses)
q = q.ColumnExpr("status.*").
// Find out who accountID follows.
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).
whereGroup := func(*bun.SelectQuery) *bun.SelectQuery {
q = q.Where("f.account_id = ?", accountID).
WhereOr("status.account_id = ?", accountID)
return q, nil
}).
return q
}
q = q.ColumnExpr("status.*").
// Find out who accountID follows.
Join("LEFT JOIN follows AS f ON f.target_account_id = status.account_id").
WhereGroup(" AND ", whereGroup).
// Sort by highest ID (newest) to lowest ID (oldest)
Order("status.id DESC")
@ -82,29 +86,19 @@ func (t *timelineDB) GetHomeTimeline(accountID string, maxID string, sinceID str
q = q.Limit(limit)
}
err := q.Select()
if err != nil {
if err == pg.ErrNoRows {
return nil, db.ErrNoEntries
}
return nil, err
return statuses, processErrorResponse(q.Scan(ctx))
}
if len(statuses) == 0 {
return nil, db.ErrNoEntries
}
return statuses, nil
}
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{}
q := t.conn.Model(&statuses).
q := t.conn.
NewSelect().
Model(&statuses).
Where("visibility = ?", gtsmodel.VisibilityPublic).
Where("? IS NULL", pg.Ident("in_reply_to_id")).
Where("? IS NULL", pg.Ident("in_reply_to_uri")).
Where("? IS NULL", pg.Ident("boost_of_id")).
Where("? IS NULL", bun.Ident("in_reply_to_id")).
Where("? IS NULL", bun.Ident("in_reply_to_uri")).
Where("? IS NULL", bun.Ident("boost_of_id")).
Order("status.id DESC")
if maxID != "" {
@ -127,28 +121,18 @@ func (t *timelineDB) GetPublicTimeline(accountID string, maxID string, sinceID s
q = q.Limit(limit)
}
err := q.Select()
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
return statuses, processErrorResponse(q.Scan(ctx))
}
// 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.
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{}
fq := t.conn.Model(&faves).
fq := t.conn.
NewSelect().
Model(&faves).
Where("account_id = ?", accountID).
Order("id DESC")
@ -164,9 +148,9 @@ func (t *timelineDB) GetFavedTimeline(accountID string, maxID string, minID stri
fq = fq.Limit(limit)
}
err := fq.Select()
err := fq.Scan(ctx)
if err != nil {
if err == pg.ErrNoRows {
if err == sql.ErrNoRows {
return nil, "", "", db.ErrNoEntries
}
return nil, "", "", err
@ -186,9 +170,13 @@ func (t *timelineDB) GetFavedTimeline(accountID string, maxID string, minID stri
}
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 == pg.ErrNoRows {
if err == sql.ErrNoRows {
return nil, "", "", db.ErrNoEntries
}
return nil, "", "", err

View file

@ -1,11 +1,30 @@
/*
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 (
"strings"
"context"
"database/sql"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/uptrace/bun"
)
// processErrorResponse parses the given error and returns an appropriate DBError.
@ -16,9 +35,40 @@ func processErrorResponse(err error) db.Error {
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

@ -34,24 +34,24 @@ type Account struct {
*/
// id of this account in the local database
ID string `pg:"type:CHAR(26),pk,notnull,unique"`
ID string `bun:"type:CHAR(26),pk,notnull,unique"`
// Username of the account, should just be a string of [a-z0-9_]. Can be added to domain to create the full username in the form ``[username]@[domain]`` eg., ``user_96@example.org``
Username string `pg:",notnull,unique:userdomain"` // username and domain should be unique *with* each other
Username string `bun:",notnull,unique:userdomain"` // username and domain should be unique *with* each other
// Domain of the account, will be null if this is a local account, otherwise something like ``example.org`` or ``mastodon.social``. Should be unique with username.
Domain string `pg:",unique:userdomain"` // username and domain should be unique *with* each other
Domain string `bun:",unique:userdomain,nullzero"` // username and domain should be unique *with* each other
/*
ACCOUNT METADATA
*/
// ID of the avatar as a media attachment
AvatarMediaAttachmentID string `pg:"type:CHAR(26)"`
AvatarMediaAttachment *MediaAttachment `pg:"rel:has-one"`
AvatarMediaAttachmentID string `bun:"type:CHAR(26)"`
AvatarMediaAttachment *MediaAttachment `bun:"rel:belongs-to"`
// For a non-local account, where can the header be fetched?
AvatarRemoteURL string
// ID of the header as a media attachment
HeaderMediaAttachmentID string `pg:"type:CHAR(26)"`
HeaderMediaAttachment *MediaAttachment `pg:"rel:has-one"`
HeaderMediaAttachmentID string `bun:"type:CHAR(26)"`
HeaderMediaAttachment *MediaAttachment `bun:"rel:belongs-to"`
// For a non-local account, where can the header be fetched?
HeaderRemoteURL string
// DisplayName for this account. Can be empty, then just the Username will be used for display purposes.
@ -63,11 +63,11 @@ type Account struct {
// Is this a memorial account, ie., has the user passed away?
Memorial bool
// This account has moved this account id in the database
MovedToAccountID string `pg:"type:CHAR(26)"`
MovedToAccountID string `bun:"type:CHAR(26)"`
// When was this account created?
CreatedAt time.Time `pg:"type:timestamp,notnull,default:now()"`
CreatedAt time.Time `bun:"type:timestamp,notnull,default:now()"`
// When was this account last updated?
UpdatedAt time.Time `pg:"type:timestamp,notnull,default:now()"`
UpdatedAt time.Time `bun:"type:timestamp,notnull,default:now()"`
// Does this account identify itself as a bot?
Bot bool
// What reason was given for signing up when this account was created?
@ -78,36 +78,36 @@ type Account struct {
*/
// Does this account need an approval for new followers?
Locked bool `pg:",default:true,use_zero"`
Locked bool `bun:",default:true"`
// Should this account be shown in the instance's profile directory?
Discoverable bool `pg:",default:false"`
Discoverable bool `bun:",default:false"`
// Default post privacy for this account
Privacy Visibility `pg:",default:'public'"`
Privacy Visibility `bun:",default:'public'"`
// Set posts from this account to sensitive by default?
Sensitive bool `pg:",default:false"`
Sensitive bool `bun:",default:false"`
// What language does this account post in?
Language string `pg:",default:'en'"`
Language string `bun:",default:'en'"`
/*
ACTIVITYPUB THINGS
*/
// What is the activitypub URI for this account discovered by webfinger?
URI string `pg:",unique"`
URI string `bun:",unique"`
// At which URL can we see the user account in a web browser?
URL string `pg:",unique"`
URL string `bun:",unique"`
// Last time this account was located using the webfinger API.
LastWebfingeredAt time.Time `pg:"type:timestamp"`
LastWebfingeredAt time.Time `bun:"type:timestamp"`
// Address of this account's activitypub inbox, for sending activity to
InboxURI string `pg:",unique"`
InboxURI string `bun:",unique"`
// Address of this account's activitypub outbox
OutboxURI string `pg:",unique"`
OutboxURI string `bun:",unique"`
// URI for getting the following list of this account
FollowingURI string `pg:",unique"`
FollowingURI string `bun:",unique"`
// URI for getting the followers list of this account
FollowersURI string `pg:",unique"`
FollowersURI string `bun:",unique"`
// URL for getting the featured collection list of this account
FeaturedCollectionURI string `pg:",unique"`
FeaturedCollectionURI string `bun:",unique"`
// What type of activitypub actor is this account?
ActorType string
// This account is associated with x account id
@ -129,15 +129,15 @@ type Account struct {
*/
// When was this account set to have all its media shown as sensitive?
SensitizedAt time.Time `pg:"type:timestamp"`
SensitizedAt time.Time `bun:"type:timestamp"`
// When was this account silenced (eg., statuses only visible to followers, not public)?
SilencedAt time.Time `pg:"type:timestamp"`
SilencedAt time.Time `bun:"type:timestamp"`
// When was this account suspended (eg., don't allow it to log in/post, don't accept media/posts from this account)
SuspendedAt time.Time `pg:"type:timestamp"`
SuspendedAt time.Time `bun:"type:timestamp"`
// Should we hide this account's collections?
HideCollections bool
// id of the database entry that caused this account to become suspended -- can be an account ID or a domain block ID
SuspensionOrigin string `pg:"type:CHAR(26)"`
SuspensionOrigin string `bun:"type:CHAR(26)"`
}
// Field represents a key value field on an account, for things like pronouns, website, etc.
@ -146,5 +146,5 @@ type Account struct {
type Field struct {
Name string
Value string
VerifiedAt time.Time `pg:"type:timestamp"`
VerifiedAt time.Time `bun:"type:timestamp"`
}

View file

@ -22,7 +22,7 @@ package gtsmodel
// It is used to authorize tokens etc, and is associated with an oauth client id in the database.
type Application struct {
// id of this application in the db
ID string `pg:"type:CHAR(26),pk,notnull"`
ID string `bun:"type:CHAR(26),pk,notnull"`
// name of the application given when it was created (eg., 'tusky')
Name string
// website for the application given when it was created (eg., 'https://tusky.app')
@ -30,7 +30,7 @@ type Application struct {
// redirect uri requested by the application for oauth2 flow
RedirectURI string
// id of the associated oauth client entity in the db
ClientID string `pg:"type:CHAR(26)"`
ClientID string `bun:"type:CHAR(26)"`
// secret of the associated oauth client entity in the db
ClientSecret string
// scopes requested when this app was created

View file

@ -5,17 +5,17 @@ import "time"
// Block refers to the blocking of one account by another.
type Block struct {
// id of this block in the database
ID string `pg:"type:CHAR(26),pk,notnull"`
ID string `bun:"type:CHAR(26),pk,notnull"`
// When was this block created
CreatedAt time.Time `pg:"type:timestamp,notnull,default:now()"`
CreatedAt time.Time `bun:"type:timestamp,notnull,default:now()"`
// When was this block updated
UpdatedAt time.Time `pg:"type:timestamp,notnull,default:now()"`
UpdatedAt time.Time `bun:"type:timestamp,notnull,default:now()"`
// Who created this block?
AccountID string `pg:"type:CHAR(26),notnull"`
Account *Account `pg:"rel:has-one"`
AccountID string `bun:"type:CHAR(26),notnull"`
Account *Account `bun:"rel:belongs-to"`
// Who is targeted by this block?
TargetAccountID string `pg:"type:CHAR(26),notnull"`
TargetAccount *Account `pg:"rel:has-one"`
TargetAccountID string `bun:"type:CHAR(26),notnull"`
TargetAccount *Account `bun:"rel:belongs-to"`
// Activitypub URI for this block
URI string `pg:",notnull"`
URI string `bun:",notnull"`
}

View file

@ -23,16 +23,16 @@ import "time"
// DomainBlock represents a federation block against a particular domain
type DomainBlock struct {
// ID of this block in the database
ID string `pg:"type:CHAR(26),pk,notnull,unique"`
ID string `bun:"type:CHAR(26),pk,notnull,unique"`
// blocked domain
Domain string `pg:",pk,notnull,unique"`
Domain string `bun:",pk,notnull,unique"`
// When was this block created
CreatedAt time.Time `pg:"type:timestamp,notnull,default:now()"`
CreatedAt time.Time `bun:"type:timestamp,notnull,default:now()"`
// When was this block updated
UpdatedAt time.Time `pg:"type:timestamp,notnull,default:now()"`
UpdatedAt time.Time `bun:"type:timestamp,notnull,default:now()"`
// Account ID of the creator of this block
CreatedByAccountID string `pg:"type:CHAR(26),notnull"`
CreatedByAccount *Account `pg:"rel:belongs-to"`
CreatedByAccountID string `bun:"type:CHAR(26),notnull"`
CreatedByAccount *Account `bun:"rel:belongs-to"`
// Private comment on this block, viewable to admins
PrivateComment string
// Public comment on this block, viewable (optionally) by everyone
@ -40,5 +40,5 @@ type DomainBlock struct {
// whether the domain name should appear obfuscated when displaying it publicly
Obfuscate bool
// if this block was created through a subscription, what's the subscription ID?
SubscriptionID string `pg:"type:CHAR(26)"`
SubscriptionID string `bun:"type:CHAR(26)"`
}

View file

@ -23,14 +23,14 @@ import "time"
// EmailDomainBlock represents a domain that the server should automatically reject sign-up requests from.
type EmailDomainBlock struct {
// ID of this block in the database
ID string `pg:"type:CHAR(26),pk,notnull,unique"`
ID string `bun:"type:CHAR(26),pk,notnull,unique"`
// Email domain to block. Eg. 'gmail.com' or 'hotmail.com'
Domain string `pg:",notnull"`
Domain string `bun:",notnull"`
// When was this block created
CreatedAt time.Time `pg:"type:timestamp,notnull,default:now()"`
CreatedAt time.Time `bun:"type:timestamp,notnull,default:now()"`
// When was this block updated
UpdatedAt time.Time `pg:"type:timestamp,notnull,default:now()"`
UpdatedAt time.Time `bun:"type:timestamp,notnull,default:now()"`
// Account ID of the creator of this block
CreatedByAccountID string `pg:"type:CHAR(26),notnull"`
CreatedByAccount *Account `pg:"rel:belongs-to"`
CreatedByAccountID string `bun:"type:CHAR(26),notnull"`
CreatedByAccount *Account `bun:"rel:belongs-to"`
}

View file

@ -23,16 +23,16 @@ import "time"
// Emoji represents a custom emoji that's been uploaded through the admin UI, and is useable by instance denizens.
type Emoji struct {
// database ID of this emoji
ID string `pg:"type:CHAR(26),pk,notnull"`
ID string `bun:"type:CHAR(26),pk,notnull"`
// String shortcode for this emoji -- the part that's between colons. This should be lowercase a-z_
// eg., 'blob_hug' 'purple_heart' Must be unique with domain.
Shortcode string `pg:",notnull,unique:shortcodedomain"`
Shortcode string `bun:",notnull,unique:shortcodedomain"`
// Origin domain of this emoji, eg 'example.org', 'queer.party'. empty string for local emojis.
Domain string `pg:",notnull,default:'',use_zero,unique:shortcodedomain"`
Domain string `bun:",notnull,default:'',use_zero,unique:shortcodedomain"`
// When was this emoji created. Must be unique with shortcode.
CreatedAt time.Time `pg:"type:timestamp,notnull,default:now()"`
CreatedAt time.Time `bun:"type:timestamp,notnull,default:now()"`
// When was this emoji updated
UpdatedAt time.Time `pg:"type:timestamp,notnull,default:now()"`
UpdatedAt time.Time `bun:"type:timestamp,notnull,default:now()"`
// Where can this emoji be retrieved remotely? Null for local emojis.
// For remote emojis, it'll be something like:
// https://hackers.town/system/custom_emojis/images/000/049/842/original/1b74481204feabfd.png
@ -51,28 +51,28 @@ type Emoji struct {
ImageStaticURL string
// Path of the emoji image in the server storage system. Will be something like:
// '/gotosocial/storage/6339820e-ef65-4166-a262-5a9f46adb1a7/emoji/original/bfa6c9c5-6c25-4ea4-98b4-d78b8126fb52.png'
ImagePath string `pg:",notnull"`
ImagePath string `bun:",notnull"`
// Path of a static version of the emoji image in the server storage system. Will be something like:
// '/gotosocial/storage/6339820e-ef65-4166-a262-5a9f46adb1a7/emoji/small/bfa6c9c5-6c25-4ea4-98b4-d78b8126fb52.png'
ImageStaticPath string `pg:",notnull"`
ImageStaticPath string `bun:",notnull"`
// MIME content type of the emoji image
// Probably "image/png"
ImageContentType string `pg:",notnull"`
ImageContentType string `bun:",notnull"`
// MIME content type of the static version of the emoji image.
ImageStaticContentType string `pg:",notnull"`
ImageStaticContentType string `bun:",notnull"`
// Size of the emoji image file in bytes, for serving purposes.
ImageFileSize int `pg:",notnull"`
ImageFileSize int `bun:",notnull"`
// Size of the static version of the emoji image file in bytes, for serving purposes.
ImageStaticFileSize int `pg:",notnull"`
ImageStaticFileSize int `bun:",notnull"`
// When was the emoji image last updated?
ImageUpdatedAt time.Time `pg:"type:timestamp,notnull,default:now()"`
ImageUpdatedAt time.Time `bun:"type:timestamp,notnull,default:now()"`
// Has a moderation action disabled this emoji from being shown?
Disabled bool `pg:",notnull,default:false"`
Disabled bool `bun:",notnull,default:false"`
// ActivityStreams uri of this emoji. Something like 'https://example.org/emojis/1234'
URI string `pg:",notnull,unique"`
URI string `bun:",notnull,unique"`
// Is this emoji visible in the admin emoji picker?
VisibleInPicker bool `pg:",notnull,default:true"`
VisibleInPicker bool `bun:",notnull,default:true"`
// In which emoji category is this emoji visible?
CategoryID string `pg:"type:CHAR(26)"`
Status *Status `pg:"rel:belongs-to"`
CategoryID string `bun:"type:CHAR(26)"`
Status *Status `bun:"rel:belongs-to"`
}

View file

@ -23,21 +23,21 @@ import "time"
// Follow represents one account following another, and the metadata around that follow.
type Follow struct {
// id of this follow in the database
ID string `pg:"type:CHAR(26),pk,notnull,unique"`
ID string `bun:"type:CHAR(26),pk,notnull,unique"`
// When was this follow created?
CreatedAt time.Time `pg:"type:timestamp,notnull,default:now()"`
CreatedAt time.Time `bun:"type:timestamp,notnull,default:now()"`
// When was this follow last updated?
UpdatedAt time.Time `pg:"type:timestamp,notnull,default:now()"`
UpdatedAt time.Time `bun:"type:timestamp,notnull,default:now()"`
// Who does this follow belong to?
AccountID string `pg:"type:CHAR(26),unique:srctarget,notnull"`
Account *Account `pg:"rel:belongs-to"`
AccountID string `bun:"type:CHAR(26),unique:srctarget,notnull"`
Account *Account `bun:"rel:belongs-to"`
// Who does AccountID follow?
TargetAccountID string `pg:"type:CHAR(26),unique:srctarget,notnull"`
TargetAccount *Account `pg:"rel:has-one"`
TargetAccountID string `bun:"type:CHAR(26),unique:srctarget,notnull"`
TargetAccount *Account `bun:"rel:belongs-to"`
// Does this follow also want to see reblogs and not just posts?
ShowReblogs bool `pg:"default:true"`
ShowReblogs bool `bun:"default:true"`
// What is the activitypub URI of this follow?
URI string `pg:",unique"`
URI string `bun:",unique"`
// does the following account want to be notified when the followed account posts?
Notify bool
}

View file

@ -23,21 +23,21 @@ import "time"
// FollowRequest represents one account requesting to follow another, and the metadata around that request.
type FollowRequest struct {
// id of this follow request in the database
ID string `pg:"type:CHAR(26),pk,notnull,unique"`
ID string `bun:"type:CHAR(26),pk,notnull,unique"`
// When was this follow request created?
CreatedAt time.Time `pg:"type:timestamp,notnull,default:now()"`
CreatedAt time.Time `bun:"type:timestamp,notnull,default:now()"`
// When was this follow request last updated?
UpdatedAt time.Time `pg:"type:timestamp,notnull,default:now()"`
UpdatedAt time.Time `bun:"type:timestamp,notnull,default:now()"`
// Who does this follow request originate from?
AccountID string `pg:"type:CHAR(26),unique:srctarget,notnull"`
Account Account `pg:"rel:has-one"`
AccountID string `bun:"type:CHAR(26),unique:srctarget,notnull"`
Account Account `bun:"rel:belongs-to"`
// Who is the target of this follow request?
TargetAccountID string `pg:"type:CHAR(26),unique:srctarget,notnull"`
TargetAccount Account `pg:"rel:has-one"`
TargetAccountID string `bun:"type:CHAR(26),unique:srctarget,notnull"`
TargetAccount Account `bun:"rel:belongs-to"`
// Does this follow also want to see reblogs and not just posts?
ShowReblogs bool `pg:"default:true"`
ShowReblogs bool `bun:"default:true"`
// What is the activitypub URI of this follow request?
URI string `pg:",unique"`
URI string `bun:",unique"`
// does the following account want to be notified when the followed account posts?
Notify bool
}

View file

@ -5,22 +5,22 @@ import "time"
// Instance represents a federated instance, either local or remote.
type Instance struct {
// ID of this instance in the database
ID string `pg:"type:CHAR(26),pk,notnull,unique"`
ID string `bun:"type:CHAR(26),pk,notnull,unique"`
// Instance domain eg example.org
Domain string `pg:",pk,notnull,unique"`
Domain string `bun:",pk,notnull,unique"`
// Title of this instance as it would like to be displayed.
Title string
// base URI of this instance eg https://example.org
URI string `pg:",notnull,unique"`
URI string `bun:",notnull,unique"`
// When was this instance created in the db?
CreatedAt time.Time `pg:"type:timestamp,notnull,default:now()"`
CreatedAt time.Time `bun:"type:timestamp,notnull,default:now()"`
// When was this instance last updated in the db?
UpdatedAt time.Time `pg:"type:timestamp,notnull,default:now()"`
UpdatedAt time.Time `bun:"type:timestamp,notnull,default:now()"`
// When was this instance suspended, if at all?
SuspendedAt time.Time
// ID of any existing domain block for this instance in the database
DomainBlockID string `pg:"type:CHAR(26)"`
DomainBlock *DomainBlock `pg:"rel:has-one"`
DomainBlockID string `bun:"type:CHAR(26)"`
DomainBlock *DomainBlock `bun:"rel:belongs-to"`
// Short description of this instance
ShortDescription string
// Longer description of this instance
@ -32,10 +32,10 @@ type Instance struct {
// Username of the contact account for this instance
ContactAccountUsername string
// Contact account ID in the database for this instance
ContactAccountID string `pg:"type:CHAR(26)"`
ContactAccount *Account `pg:"rel:has-one"`
ContactAccountID string `bun:"type:CHAR(26)"`
ContactAccount *Account `bun:"rel:belongs-to"`
// Reputation score of this instance
Reputation int64 `pg:",notnull,default:0"`
Reputation int64 `bun:",notnull,default:0"`
// Version of the software used on this instance
Version string
}

View file

@ -26,28 +26,28 @@ import (
// somewhere in storage and that can be retrieved and served by the router.
type MediaAttachment struct {
// ID of the attachment in the database
ID string `pg:"type:CHAR(26),pk,notnull,unique"`
ID string `bun:"type:CHAR(26),pk,notnull,unique"`
// ID of the status to which this is attached
StatusID string `pg:"type:CHAR(26)"`
StatusID string `bun:"type:CHAR(26)"`
// Where can the attachment be retrieved on *this* server
URL string
// Where can the attachment be retrieved on a remote server (empty for local media)
RemoteURL string
// When was the attachment created
CreatedAt time.Time `pg:"type:timestamp,notnull,default:now()"`
CreatedAt time.Time `bun:"type:timestamp,notnull,default:now()"`
// When was the attachment last updated
UpdatedAt time.Time `pg:"type:timestamp,notnull,default:now()"`
UpdatedAt time.Time `bun:"type:timestamp,notnull,default:now()"`
// Type of file (image/gif/audio/video)
Type FileType `pg:",notnull"`
Type FileType `bun:",notnull"`
// Metadata about the file
FileMeta FileMeta
// To which account does this attachment belong
AccountID string `pg:"type:CHAR(26),notnull"`
Account *Account `pg:"rel:belongs-to"`
AccountID string `bun:"type:CHAR(26),notnull"`
Account *Account `bun:"rel:belongs-to"`
// Description of the attachment (for screenreaders)
Description string
// To which scheduled status does this attachment belong
ScheduledStatusID string `pg:"type:CHAR(26)"`
ScheduledStatusID string `bun:"type:CHAR(26)"`
// What is the generated blurhash of this attachment
Blurhash string
// What is the processing status of this attachment
@ -71,7 +71,7 @@ type File struct {
// What is the size of the file in bytes.
FileSize int
// When was the file last updated.
UpdatedAt time.Time `pg:"type:timestamp,notnull,default:now()"`
UpdatedAt time.Time `bun:"type:timestamp,notnull,default:now()"`
}
// Thumbnail refers to a small image thumbnail derived from a larger image, video, or audio file.
@ -83,7 +83,7 @@ type Thumbnail struct {
// What is the size of the file in bytes
FileSize int
// When was the file last updated
UpdatedAt time.Time `pg:"type:timestamp,notnull,default:now()"`
UpdatedAt time.Time `bun:"type:timestamp,notnull,default:now()"`
// What is the URL of the thumbnail on the local server
URL string
// What is the remote URL of the thumbnail (empty for local media)

View file

@ -23,22 +23,22 @@ import "time"
// Mention refers to the 'tagging' or 'mention' of a user within a status.
type Mention struct {
// ID of this mention in the database
ID string `pg:"type:CHAR(26),pk,notnull,unique"`
ID string `bun:"type:CHAR(26),pk,notnull,unique"`
// ID of the status this mention originates from
StatusID string `pg:"type:CHAR(26),notnull"`
Status *Status `pg:"rel:belongs-to"`
StatusID string `bun:"type:CHAR(26),notnull"`
Status *Status `bun:"rel:belongs-to"`
// When was this mention created?
CreatedAt time.Time `pg:"type:timestamp,notnull,default:now()"`
CreatedAt time.Time `bun:"type:timestamp,notnull,default:now()"`
// When was this mention last updated?
UpdatedAt time.Time `pg:"type:timestamp,notnull,default:now()"`
UpdatedAt time.Time `bun:"type:timestamp,notnull,default:now()"`
// What's the internal account ID of the originator of the mention?
OriginAccountID string `pg:"type:CHAR(26),notnull"`
OriginAccount *Account `pg:"rel:has-one"`
OriginAccountID string `bun:"type:CHAR(26),notnull"`
OriginAccount *Account `bun:"rel:belongs-to"`
// What's the AP URI of the originator of the mention?
OriginAccountURI string `pg:",notnull"`
OriginAccountURI string `bun:",notnull"`
// What's the internal account ID of the mention target?
TargetAccountID string `pg:"type:CHAR(26),notnull"`
TargetAccount *Account `pg:"rel:has-one"`
TargetAccountID string `bun:"type:CHAR(26),notnull"`
TargetAccount *Account `bun:"rel:belongs-to"`
// Prevent this mention from generating a notification?
Silent bool
@ -54,15 +54,15 @@ type Mention struct {
// @whatever_username@example.org
//
// This will not be put in the database, it's just for convenience.
NameString string `pg:"-"`
NameString string `bun:"-"`
// TargetAccountURI is the AP ID (uri) of the user mentioned.
//
// This will not be put in the database, it's just for convenience.
TargetAccountURI string `pg:"-"`
TargetAccountURI string `bun:"-"`
// TargetAccountURL is the web url of the user mentioned.
//
// This will not be put in the database, it's just for convenience.
TargetAccountURL string `pg:"-"`
TargetAccountURL string `bun:"-"`
// A pointer to the gtsmodel account of the mentioned account.
}

View file

@ -23,20 +23,20 @@ import "time"
// Notification models an alert/notification sent to an account about something like a reblog, like, new follow request, etc.
type Notification struct {
// ID of this notification in the database
ID string `pg:"type:CHAR(26),pk,notnull"`
ID string `bun:"type:CHAR(26),pk,notnull"`
// Type of this notification
NotificationType NotificationType `pg:",notnull"`
NotificationType NotificationType `bun:",notnull"`
// Creation time of this notification
CreatedAt time.Time `pg:"type:timestamp,notnull,default:now()"`
CreatedAt time.Time `bun:"type:timestamp,notnull,default:now()"`
// Which account does this notification target (ie., who will receive the notification?)
TargetAccountID string `pg:"type:CHAR(26),notnull"`
TargetAccount *Account `pg:"rel:has-one"`
TargetAccountID string `bun:"type:CHAR(26),notnull"`
TargetAccount *Account `bun:"rel:belongs-to"`
// Which account performed the action that created this notification?
OriginAccountID string `pg:"type:CHAR(26),notnull"`
OriginAccount *Account `pg:"rel:has-one"`
OriginAccountID string `bun:"type:CHAR(26),notnull"`
OriginAccount *Account `bun:"rel:belongs-to"`
// If the notification pertains to a status, what is the database ID of that status?
StatusID string `pg:"type:CHAR(26)"`
Status *Status `pg:"rel:has-one"`
StatusID string `bun:"type:CHAR(26)"`
Status *Status `bun:"rel:belongs-to"`
// Has this notification been read already?
Read bool
}

View file

@ -20,7 +20,7 @@ package gtsmodel
// RouterSession is used to store and retrieve settings for a router session.
type RouterSession struct {
ID string `pg:"type:CHAR(26),pk,notnull"`
Auth []byte `pg:",notnull"`
Crypt []byte `pg:",notnull"`
ID string `bun:"type:CHAR(26),pk,notnull"`
Auth []byte `bun:",notnull"`
Crypt []byte `bun:",notnull"`
}

View file

@ -25,61 +25,61 @@ import (
// Status represents a user-created 'post' or 'status' in the database, either remote or local
type Status struct {
// id of the status in the database
ID string `pg:"type:CHAR(26),pk,notnull"`
ID string `bun:"type:CHAR(26),pk,notnull"`
// uri at which this status is reachable
URI string `pg:",unique"`
URI string `bun:",unique"`
// web url for viewing this status
URL string `pg:",unique"`
URL string `bun:",unique"`
// the html-formatted content of this status
Content string
// Database IDs of any media attachments associated with this status
AttachmentIDs []string `pg:"attachments,array"`
Attachments []*MediaAttachment `pg:"attached_media,rel:has-many"`
AttachmentIDs []string `bun:"attachments,array"`
Attachments []*MediaAttachment `bun:"attached_media,rel:has-many"`
// Database IDs of any tags used in this status
TagIDs []string `pg:"tags,array"`
Tags []*Tag `pg:"attached_tags,many2many:status_to_tags"` // https://pg.uptrace.dev/orm/many-to-many-relation/
TagIDs []string `bun:"tags,array"`
Tags []*Tag `bun:"attached_tags,m2m:status_to_tags"` // https://pg.uptrace.dev/orm/many-to-many-relation/
// Database IDs of any mentions in this status
MentionIDs []string `pg:"mentions,array"`
Mentions []*Mention `pg:"attached_mentions,rel:has-many"`
MentionIDs []string `bun:"mentions,array"`
Mentions []*Mention `bun:"attached_mentions,rel:has-many"`
// Database IDs of any emojis used in this status
EmojiIDs []string `pg:"emojis,array"`
Emojis []*Emoji `pg:"attached_emojis,many2many:status_to_emojis"` // https://pg.uptrace.dev/orm/many-to-many-relation/
EmojiIDs []string `bun:"emojis,array"`
Emojis []*Emoji `bun:"attached_emojis,m2m:status_to_emojis"` // https://pg.uptrace.dev/orm/many-to-many-relation/
// when was this status created?
CreatedAt time.Time `pg:"type:timestamp,notnull,default:now()"`
CreatedAt time.Time `bun:"type:timestamp,notnull,default:now()"`
// when was this status updated?
UpdatedAt time.Time `pg:"type:timestamp,notnull,default:now()"`
UpdatedAt time.Time `bun:"type:timestamp,notnull,default:now()"`
// is this status from a local account?
Local bool
// which account posted this status?
AccountID string `pg:"type:CHAR(26),notnull"`
Account *Account `pg:"rel:has-one"`
AccountID string `bun:"type:CHAR(26),notnull"`
Account *Account `bun:"rel:belongs-to"`
// AP uri of the owner of this status
AccountURI string
// id of the status this status is a reply to
InReplyToID string `pg:"type:CHAR(26)"`
InReplyTo *Status `pg:"rel:has-one"`
InReplyToID string `bun:"type:CHAR(26)"`
InReplyTo *Status `bun:"rel:belongs-to"`
// AP uri of the status this status is a reply to
InReplyToURI string
// id of the account that this status replies to
InReplyToAccountID string `pg:"type:CHAR(26)"`
InReplyToAccount *Account `pg:"rel:has-one"`
InReplyToAccountID string `bun:"type:CHAR(26)"`
InReplyToAccount *Account `bun:"rel:belongs-to"`
// id of the status this status is a boost of
BoostOfID string `pg:"type:CHAR(26)"`
BoostOf *Status `pg:"rel:has-one"`
BoostOfID string `bun:"type:CHAR(26)"`
BoostOf *Status `bun:"rel:belongs-to"`
// id of the account that owns the boosted status
BoostOfAccountID string `pg:"type:CHAR(26)"`
BoostOfAccount *Account `pg:"rel:has-one"`
BoostOfAccountID string `bun:"type:CHAR(26)"`
BoostOfAccount *Account `bun:"rel:belongs-to"`
// cw string for this status
ContentWarning string
// visibility entry for this status
Visibility Visibility `pg:",notnull"`
Visibility Visibility `bun:",notnull"`
// mark the status as sensitive?
Sensitive bool
// what language is this status written in?
Language string
// Which application was used to create this status?
CreatedWithApplicationID string `pg:"type:CHAR(26)"`
CreatedWithApplication *Application `pg:"rel:has-one"`
CreatedWithApplicationID string `bun:"type:CHAR(26)"`
CreatedWithApplication *Application `bun:"rel:belongs-to"`
// advanced visibility for this status
VisibilityAdvanced *VisibilityAdvanced
// What is the activitystreams type of this status? See: https://www.w3.org/TR/activitystreams-vocabulary/#object-types
@ -93,14 +93,18 @@ type Status struct {
// StatusToTag is an intermediate struct to facilitate the many2many relationship between a status and one or more tags.
type StatusToTag struct {
StatusID string `pg:"unique:statustag"`
TagID string `pg:"unique:statustag"`
StatusID string `bun:"type:CHAR(26),unique:statustag"`
Status *Status `bun:"rel:has-one"`
TagID string `bun:"type:CHAR(26),unique:statustag"`
Tag *Tag `bun:"rel:has-one"`
}
// StatusToEmoji is an intermediate struct to facilitate the many2many relationship between a status and one or more emojis.
type StatusToEmoji struct {
StatusID string `pg:"unique:statusemoji"`
EmojiID string `pg:"unique:statusemoji"`
StatusID string `bun:"type:CHAR(26),unique:statusemoji"`
Status *Status `bun:"rel:has-one"`
EmojiID string `bun:"type:CHAR(26),unique:statusemoji"`
Emoji *Emoji `bun:"rel:has-one"`
}
// Visibility represents the visibility granularity of a status.
@ -134,11 +138,11 @@ const (
// If DIRECT is selected, boostable will be FALSE, and all other flags will be TRUE.
type VisibilityAdvanced struct {
// This status will be federated beyond the local timeline(s)
Federated bool `pg:"default:true"`
Federated bool `bun:"default:true"`
// This status can be boosted/reblogged
Boostable bool `pg:"default:true"`
Boostable bool `bun:"default:true"`
// This status can be replied to
Replyable bool `pg:"default:true"`
Replyable bool `bun:"default:true"`
// This status can be liked/faved
Likeable bool `pg:"default:true"`
Likeable bool `bun:"default:true"`
}

View file

@ -23,15 +23,15 @@ import "time"
// StatusBookmark refers to one account having a 'bookmark' of the status of another account
type StatusBookmark struct {
// id of this bookmark in the database
ID string `pg:"type:CHAR(26),pk,notnull,unique"`
ID string `bun:"type:CHAR(26),pk,notnull,unique"`
// when was this bookmark created
CreatedAt time.Time `pg:"type:timestamp,notnull,default:now()"`
CreatedAt time.Time `bun:"type:timestamp,notnull,default:now()"`
// id of the account that created ('did') the bookmarking
AccountID string `pg:"type:CHAR(26),notnull"`
Account *Account `pg:"rel:belongs-to"`
AccountID string `bun:"type:CHAR(26),notnull"`
Account *Account `bun:"rel:belongs-to"`
// id the account owning the bookmarked status
TargetAccountID string `pg:"type:CHAR(26),notnull"`
TargetAccount *Account `pg:"rel:has-one"`
TargetAccountID string `bun:"type:CHAR(26),notnull"`
TargetAccount *Account `bun:"rel:belongs-to"`
// database id of the status that has been bookmarked
StatusID string `pg:"type:CHAR(26),notnull"`
StatusID string `bun:"type:CHAR(26),notnull"`
}

View file

@ -23,18 +23,18 @@ import "time"
// StatusFave refers to a 'fave' or 'like' in the database, from one account, targeting the status of another account
type StatusFave struct {
// id of this fave in the database
ID string `pg:"type:CHAR(26),pk,notnull,unique"`
ID string `bun:"type:CHAR(26),pk,notnull,unique"`
// when was this fave created
CreatedAt time.Time `pg:"type:timestamp,notnull,default:now()"`
CreatedAt time.Time `bun:"type:timestamp,notnull,default:now()"`
// id of the account that created ('did') the fave
AccountID string `pg:"type:CHAR(26),notnull"`
Account *Account `pg:"rel:has-one"`
AccountID string `bun:"type:CHAR(26),notnull"`
Account *Account `bun:"rel:belongs-to"`
// id the account owning the faved status
TargetAccountID string `pg:"type:CHAR(26),notnull"`
TargetAccount *Account `pg:"rel:has-one"`
TargetAccountID string `bun:"type:CHAR(26),notnull"`
TargetAccount *Account `bun:"rel:belongs-to"`
// database id of the status that has been 'faved'
StatusID string `pg:"type:CHAR(26),notnull"`
Status *Status `pg:"rel:has-one"`
StatusID string `bun:"type:CHAR(26),notnull"`
Status *Status `bun:"rel:belongs-to"`
// ActivityPub URI of this fave
URI string `pg:",notnull"`
URI string `bun:",notnull"`
}

View file

@ -23,16 +23,16 @@ import "time"
// StatusMute refers to one account having muted the status of another account or its own
type StatusMute struct {
// id of this mute in the database
ID string `pg:"type:CHAR(26),pk,notnull,unique"`
ID string `bun:"type:CHAR(26),pk,notnull,unique"`
// when was this mute created
CreatedAt time.Time `pg:"type:timestamp,notnull,default:now()"`
CreatedAt time.Time `bun:"type:timestamp,notnull,default:now()"`
// id of the account that created ('did') the mute
AccountID string `pg:"type:CHAR(26),notnull"`
Account *Account `pg:"rel:belongs-to"`
AccountID string `bun:"type:CHAR(26),notnull"`
Account *Account `bun:"rel:belongs-to"`
// id the account owning the muted status (can be the same as accountID)
TargetAccountID string `pg:"type:CHAR(26),notnull"`
TargetAccount *Account `pg:"rel:has-one"`
TargetAccountID string `bun:"type:CHAR(26),notnull"`
TargetAccount *Account `bun:"rel:belongs-to"`
// database id of the status that has been muted
StatusID string `pg:"type:CHAR(26),notnull"`
Status *Status `pg:"rel:has-one"`
StatusID string `bun:"type:CHAR(26),notnull"`
Status *Status `bun:"rel:belongs-to"`
}

View file

@ -23,21 +23,21 @@ import "time"
// Tag represents a hashtag for gathering public statuses together
type Tag struct {
// id of this tag in the database
ID string `pg:",unique,type:CHAR(26),pk,notnull"`
ID string `bun:",unique,type:CHAR(26),pk,notnull"`
// Href of this tag, eg https://example.org/tags/somehashtag
URL string
// name of this tag -- the tag without the hash part
Name string `pg:",unique,notnull"`
Name string `bun:",unique,notnull"`
// Which account ID is the first one we saw using this tag?
FirstSeenFromAccountID string `pg:"type:CHAR(26)"`
FirstSeenFromAccountID string `bun:"type:CHAR(26)"`
// when was this tag created
CreatedAt time.Time `pg:"type:timestamp,notnull,default:now()"`
CreatedAt time.Time `bun:"type:timestamp,notnull,default:now()"`
// when was this tag last updated
UpdatedAt time.Time `pg:"type:timestamp,notnull,default:now()"`
UpdatedAt time.Time `bun:"type:timestamp,notnull,default:now()"`
// can our instance users use this tag?
Useable bool `pg:",notnull,default:true"`
Useable bool `bun:",notnull,default:true"`
// can our instance users look up this tag?
Listable bool `pg:",notnull,default:true"`
Listable bool `bun:",notnull,default:true"`
// when was this tag last used?
LastStatusAt time.Time `pg:"type:timestamp,notnull,default:now()"`
LastStatusAt time.Time `bun:"type:timestamp,notnull,default:now()"`
}

View file

@ -31,37 +31,37 @@ type User struct {
*/
// id of this user in the local database; the end-user will never need to know this, it's strictly internal
ID string `pg:"type:CHAR(26),pk,notnull,unique"`
ID string `bun:"type:CHAR(26),pk,notnull,unique"`
// confirmed email address for this user, this should be unique -- only one email address registered per instance, multiple users per email are not supported
Email string `pg:"default:null,unique"`
Email string `bun:"default:null,unique"`
// The id of the local gtsmodel.Account entry for this user, if it exists (unconfirmed users don't have an account yet)
AccountID string `pg:"type:CHAR(26),unique"`
Account *Account `pg:"rel:has-one"`
AccountID string `bun:"type:CHAR(26),unique"`
Account *Account `bun:"rel:belongs-to"`
// The encrypted password of this user, generated using https://pkg.go.dev/golang.org/x/crypto/bcrypt#GenerateFromPassword. A salt is included so we're safe against 🌈 tables
EncryptedPassword string `pg:",notnull"`
EncryptedPassword string `bun:",notnull"`
/*
USER METADATA
*/
// When was this user created?
CreatedAt time.Time `pg:"type:timestamp,notnull,default:now()"`
CreatedAt time.Time `bun:"type:timestamp,notnull,default:now()"`
// From what IP was this user created?
SignUpIP net.IP
// When was this user updated (eg., password changed, email address changed)?
UpdatedAt time.Time `pg:"type:timestamp,notnull,default:now()"`
UpdatedAt time.Time `bun:"type:timestamp,notnull,default:now()"`
// When did this user sign in for their current session?
CurrentSignInAt time.Time `pg:"type:timestamp"`
CurrentSignInAt time.Time `bun:"type:timestamp"`
// What's the most recent IP of this user
CurrentSignInIP net.IP
// When did this user last sign in?
LastSignInAt time.Time `pg:"type:timestamp"`
LastSignInAt time.Time `bun:"type:timestamp"`
// What's the previous IP of this user?
LastSignInIP net.IP
// How many times has this user signed in?
SignInCount int
// id of the user who invited this user (who let this guy in?)
InviteID string `pg:"type:CHAR(26)"`
InviteID string `bun:"type:CHAR(26)"`
// What languages does this user want to see?
ChosenLanguages []string
// What languages does this user not want to see?
@ -69,10 +69,10 @@ type User struct {
// In what timezone/locale is this user located?
Locale string
// Which application id created this user? See gtsmodel.Application
CreatedByApplicationID string `pg:"type:CHAR(26)"`
CreatedByApplication *Application `pg:"rel:has-one"`
CreatedByApplicationID string `bun:"type:CHAR(26)"`
CreatedByApplication *Application `bun:"rel:belongs-to"`
// When did we last contact this user
LastEmailedAt time.Time `pg:"type:timestamp"`
LastEmailedAt time.Time `bun:"type:timestamp"`
/*
USER CONFIRMATION
@ -81,9 +81,9 @@ type User struct {
// What confirmation token did we send this user/what are we expecting back?
ConfirmationToken string
// When did the user confirm their email address
ConfirmedAt time.Time `pg:"type:timestamp"`
ConfirmedAt time.Time `bun:"type:timestamp"`
// When did we send email confirmation to this user?
ConfirmationSentAt time.Time `pg:"type:timestamp"`
ConfirmationSentAt time.Time `bun:"type:timestamp"`
// Email address that hasn't yet been confirmed
UnconfirmedEmail string
@ -107,7 +107,7 @@ type User struct {
// The generated token that the user can use to reset their password
ResetPasswordToken string
// When did we email the user their reset-password email?
ResetPasswordSentAt time.Time `pg:"type:timestamp"`
ResetPasswordSentAt time.Time `bun:"type:timestamp"`
EncryptedOTPSecret string
EncryptedOTPSecretIv string
@ -117,6 +117,6 @@ type User struct {
ConsumedTimestamp int
RememberToken string
SignInToken string
SignInTokenSentAt time.Time `pg:"type:timestamp"`
SignInTokenSentAt time.Time `bun:"type:timestamp"`
WebauthnID string
}

View file

@ -48,7 +48,7 @@ func (p *processor) Get(ctx context.Context, requestingAccount *gtsmodel.Account
var mastoAccount *apimodel.Account
if blocked {
mastoAccount, err = p.tc.AccountToMastoBlocked(targetAccount)
mastoAccount, err = p.tc.AccountToMastoBlocked(ctx, targetAccount)
if err != nil {
return nil, fmt.Errorf("error converting account: %s", err)
}
@ -57,16 +57,16 @@ func (p *processor) Get(ctx context.Context, requestingAccount *gtsmodel.Account
// last-minute check to make sure we have remote account header/avi cached
if targetAccount.Domain != "" {
a, err := p.federator.EnrichRemoteAccount(requestingAccount.Username, targetAccount)
a, err := p.federator.EnrichRemoteAccount(ctx, requestingAccount.Username, targetAccount)
if err == nil {
targetAccount = a
}
}
if requestingAccount != nil && targetAccount.ID == requestingAccount.ID {
mastoAccount, err = p.tc.AccountToMastoSensitive(targetAccount)
mastoAccount, err = p.tc.AccountToMastoSensitive(ctx, targetAccount)
} else {
mastoAccount, err = p.tc.AccountToMastoPublic(targetAccount)
mastoAccount, err = p.tc.AccountToMastoPublic(ctx, targetAccount)
}
if err != nil {
return nil, fmt.Errorf("error converting account: %s", err)

View file

@ -64,7 +64,7 @@ func (p *processor) FollowersGet(ctx context.Context, requestingAccount *gtsmode
f.Account = a
}
account, err := p.tc.AccountToMastoPublic(f.Account)
account, err := p.tc.AccountToMastoPublic(ctx, f.Account)
if err != nil {
return nil, gtserror.NewErrorInternalError(err)
}

View file

@ -64,7 +64,7 @@ func (p *processor) FollowingGet(ctx context.Context, requestingAccount *gtsmode
f.TargetAccount = a
}
account, err := p.tc.AccountToMastoPublic(f.TargetAccount)
account, err := p.tc.AccountToMastoPublic(ctx, f.TargetAccount)
if err != nil {
return nil, gtserror.NewErrorInternalError(err)
}

View file

@ -38,7 +38,7 @@ func (p *processor) RelationshipGet(ctx context.Context, requestingAccount *gtsm
return nil, gtserror.NewErrorInternalError(fmt.Errorf("error getting relationship: %s", err))
}
r, err := p.tc.RelationshipToMasto(gtsR)
r, err := p.tc.RelationshipToMasto(ctx, gtsR)
if err != nil {
return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting relationship: %s", err))
}

View file

@ -46,12 +46,12 @@ func (p *processor) StatusesGet(ctx context.Context, requestingAccount *gtsmodel
}
for _, s := range statuses {
visible, err := p.filter.StatusVisible(s, requestingAccount)
visible, err := p.filter.StatusVisible(ctx, s, requestingAccount)
if err != nil || !visible {
continue
}
apiStatus, err := p.tc.StatusToMasto(s, requestingAccount)
apiStatus, err := p.tc.StatusToMasto(ctx, s, requestingAccount)
if err != nil {
return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting status to masto: %s", err))
}

View file

@ -129,7 +129,7 @@ func (p *processor) Update(ctx context.Context, account *gtsmodel.Account, form
OriginAccount: updatedAccount,
}
acctSensitive, err := p.tc.AccountToMastoSensitive(updatedAccount)
acctSensitive, err := p.tc.AccountToMastoSensitive(ctx, updatedAccount)
if err != nil {
return nil, fmt.Errorf("could not convert account into mastosensitive account: %s", err)
}
@ -161,7 +161,7 @@ func (p *processor) UpdateAvatar(ctx context.Context, avatar *multipart.FileHead
}
// do the setting
avatarInfo, err := p.mediaHandler.ProcessHeaderOrAvatar(buf.Bytes(), accountID, media.Avatar, "")
avatarInfo, err := p.mediaHandler.ProcessHeaderOrAvatar(ctx, buf.Bytes(), accountID, media.Avatar, "")
if err != nil {
return nil, fmt.Errorf("error processing avatar: %s", err)
}
@ -194,7 +194,7 @@ func (p *processor) UpdateHeader(ctx context.Context, header *multipart.FileHead
}
// do the setting
headerInfo, err := p.mediaHandler.ProcessHeaderOrAvatar(buf.Bytes(), accountID, media.Header, "")
headerInfo, err := p.mediaHandler.ProcessHeaderOrAvatar(ctx, buf.Bytes(), accountID, media.Header, "")
if err != nil {
return nil, fmt.Errorf("error processing header: %s", err)
}

View file

@ -71,7 +71,7 @@ func (p *processor) DomainBlockCreate(ctx context.Context, account *gtsmodel.Acc
go p.initiateDomainBlockSideEffects(ctx, account, domainBlock) // TODO: add this to a queuing system so it can retry/resume
}
mastoDomainBlock, err := p.tc.DomainBlockToMasto(domainBlock, false)
mastoDomainBlock, err := p.tc.DomainBlockToMasto(ctx, domainBlock, false)
if err != nil {
return nil, gtserror.NewErrorInternalError(fmt.Errorf("DomainBlockCreate: error converting domain block to frontend/masto representation %s: %s", domain, err))
}

View file

@ -42,7 +42,7 @@ func (p *processor) DomainBlockDelete(ctx context.Context, account *gtsmodel.Acc
}
// prepare the domain block to return
mastoDomainBlock, err := p.tc.DomainBlockToMasto(domainBlock, false)
mastoDomainBlock, err := p.tc.DomainBlockToMasto(ctx, domainBlock, false)
if err != nil {
return nil, gtserror.NewErrorInternalError(err)
}

View file

@ -50,7 +50,7 @@ func (p *processor) EmojiCreate(ctx context.Context, account *gtsmodel.Account,
}
// allow the mediaHandler to work its magic of processing the emoji bytes, and putting them in whatever storage backend we're using
emoji, err := p.mediaHandler.ProcessLocalEmoji(buf.Bytes(), form.Shortcode)
emoji, err := p.mediaHandler.ProcessLocalEmoji(ctx, buf.Bytes(), form.Shortcode)
if err != nil {
return nil, fmt.Errorf("error reading emoji: %s", err)
}
@ -61,7 +61,7 @@ func (p *processor) EmojiCreate(ctx context.Context, account *gtsmodel.Account,
}
emoji.ID = emojiID
mastoEmoji, err := p.tc.EmojiToMasto(emoji)
mastoEmoji, err := p.tc.EmojiToMasto(ctx, emoji)
if err != nil {
return nil, fmt.Errorf("error converting emoji to mastotype: %s", err)
}

View file

@ -40,7 +40,7 @@ func (p *processor) DomainBlockGet(ctx context.Context, account *gtsmodel.Accoun
return nil, gtserror.NewErrorNotFound(fmt.Errorf("no entry for ID %s", id))
}
mastoDomainBlock, err := p.tc.DomainBlockToMasto(domainBlock, export)
mastoDomainBlock, err := p.tc.DomainBlockToMasto(ctx, domainBlock, export)
if err != nil {
return nil, gtserror.NewErrorInternalError(err)
}

View file

@ -39,7 +39,7 @@ func (p *processor) DomainBlocksGet(ctx context.Context, account *gtsmodel.Accou
mastoDomainBlocks := []*apimodel.DomainBlock{}
for _, b := range domainBlocks {
mastoDomainBlock, err := p.tc.DomainBlockToMasto(b, export)
mastoDomainBlock, err := p.tc.DomainBlockToMasto(ctx, b, export)
if err != nil {
return nil, gtserror.NewErrorInternalError(err)
}

View file

@ -80,7 +80,7 @@ func (p *processor) AppCreate(ctx context.Context, authed *oauth.Auth, form *api
return nil, err
}
mastoApp, err := p.tc.AppToMastoSensitive(app)
mastoApp, err := p.tc.AppToMastoSensitive(ctx, app)
if err != nil {
return nil, err
}

View file

@ -44,7 +44,7 @@ func (p *processor) BlocksGet(ctx context.Context, authed *oauth.Auth, maxID str
apiAccounts := []*apimodel.Account{}
for _, a := range accounts {
apiAccount, err := p.tc.AccountToMastoBlocked(a)
apiAccount, err := p.tc.AccountToMastoBlocked(ctx, a)
if err != nil {
continue
}

View file

@ -44,7 +44,7 @@ func (p *processor) GetFediUser(ctx context.Context, requestedUsername string, r
var requestedPerson vocab.ActivityStreamsPerson
if util.IsPublicKeyPath(requestURL) {
// if it's a public key path, we don't need to authenticate but we'll only serve the bare minimum user profile needed for the public key
requestedPerson, err = p.tc.AccountToASMinimal(requestedAccount)
requestedPerson, err = p.tc.AccountToASMinimal(ctx, requestedAccount)
if err != nil {
return nil, gtserror.NewErrorInternalError(err)
}
@ -56,8 +56,8 @@ func (p *processor) GetFediUser(ctx context.Context, requestedUsername string, r
}
// if we're not already handshaking/dereferencing a remote account, dereference it now
if !p.federator.Handshaking(requestedUsername, requestingAccountURI) {
requestingAccount, _, err := p.federator.GetRemoteAccount(requestedUsername, requestingAccountURI, false)
if !p.federator.Handshaking(ctx, requestedUsername, requestingAccountURI) {
requestingAccount, _, err := p.federator.GetRemoteAccount(ctx, requestedUsername, requestingAccountURI, false)
if err != nil {
return nil, gtserror.NewErrorNotAuthorized(err)
}
@ -72,7 +72,7 @@ func (p *processor) GetFediUser(ctx context.Context, requestedUsername string, r
}
}
requestedPerson, err = p.tc.AccountToAS(requestedAccount)
requestedPerson, err = p.tc.AccountToAS(ctx, requestedAccount)
if err != nil {
return nil, gtserror.NewErrorInternalError(err)
}
@ -101,7 +101,7 @@ func (p *processor) GetFediFollowers(ctx context.Context, requestedUsername stri
return nil, gtserror.NewErrorNotAuthorized(errors.New("not authorized"), "not authorized")
}
requestingAccount, _, err := p.federator.GetRemoteAccount(requestedUsername, requestingAccountURI, false)
requestingAccount, _, err := p.federator.GetRemoteAccount(ctx, requestedUsername, requestingAccountURI, false)
if err != nil {
return nil, gtserror.NewErrorNotAuthorized(err)
}
@ -146,7 +146,7 @@ func (p *processor) GetFediFollowing(ctx context.Context, requestedUsername stri
return nil, gtserror.NewErrorNotAuthorized(errors.New("not authorized"), "not authorized")
}
requestingAccount, _, err := p.federator.GetRemoteAccount(requestedUsername, requestingAccountURI, false)
requestingAccount, _, err := p.federator.GetRemoteAccount(ctx, requestedUsername, requestingAccountURI, false)
if err != nil {
return nil, gtserror.NewErrorNotAuthorized(err)
}
@ -191,7 +191,7 @@ func (p *processor) GetFediStatus(ctx context.Context, requestedUsername string,
return nil, gtserror.NewErrorNotAuthorized(errors.New("not authorized"), "not authorized")
}
requestingAccount, _, err := p.federator.GetRemoteAccount(requestedUsername, requestingAccountURI, false)
requestingAccount, _, err := p.federator.GetRemoteAccount(ctx, requestedUsername, requestingAccountURI, false)
if err != nil {
return nil, gtserror.NewErrorNotAuthorized(err)
}
@ -216,7 +216,7 @@ func (p *processor) GetFediStatus(ctx context.Context, requestedUsername string,
return nil, gtserror.NewErrorNotFound(fmt.Errorf("database error getting status with id %s and account id %s: %s", requestedStatusID, requestedAccount.ID, err))
}
visible, err := p.filter.StatusVisible(s, requestingAccount)
visible, err := p.filter.StatusVisible(ctx, s, requestingAccount)
if err != nil {
return nil, gtserror.NewErrorInternalError(err)
}
@ -225,7 +225,7 @@ func (p *processor) GetFediStatus(ctx context.Context, requestedUsername string,
}
// requester is authorized to view the status, so convert it to AP representation and serialize it
asStatus, err := p.tc.StatusToAS(s)
asStatus, err := p.tc.StatusToAS(ctx, s)
if err != nil {
return nil, gtserror.NewErrorInternalError(err)
}
@ -251,7 +251,7 @@ func (p *processor) GetFediStatusReplies(ctx context.Context, requestedUsername
return nil, gtserror.NewErrorNotAuthorized(errors.New("not authorized"), "not authorized")
}
requestingAccount, _, err := p.federator.GetRemoteAccount(requestedUsername, requestingAccountURI, false)
requestingAccount, _, err := p.federator.GetRemoteAccount(ctx, requestedUsername, requestingAccountURI, false)
if err != nil {
return nil, gtserror.NewErrorNotAuthorized(err)
}
@ -276,7 +276,7 @@ func (p *processor) GetFediStatusReplies(ctx context.Context, requestedUsername
return nil, gtserror.NewErrorNotFound(fmt.Errorf("database error getting status with id %s and account id %s: %s", requestedStatusID, requestedAccount.ID, err))
}
visible, err := p.filter.StatusVisible(s, requestingAccount)
visible, err := p.filter.StatusVisible(ctx, s, requestingAccount)
if err != nil {
return nil, gtserror.NewErrorInternalError(err)
}
@ -295,7 +295,7 @@ func (p *processor) GetFediStatusReplies(ctx context.Context, requestedUsername
// scenario 1
// get the collection
collection, err := p.tc.StatusToASRepliesCollection(s, onlyOtherAccounts)
collection, err := p.tc.StatusToASRepliesCollection(ctx, s, onlyOtherAccounts)
if err != nil {
return nil, gtserror.NewErrorInternalError(err)
}
@ -308,7 +308,7 @@ func (p *processor) GetFediStatusReplies(ctx context.Context, requestedUsername
// scenario 2
// get the collection
collection, err := p.tc.StatusToASRepliesCollection(s, onlyOtherAccounts)
collection, err := p.tc.StatusToASRepliesCollection(ctx, s, onlyOtherAccounts)
if err != nil {
return nil, gtserror.NewErrorInternalError(err)
}
@ -339,13 +339,13 @@ func (p *processor) GetFediStatusReplies(ctx context.Context, requestedUsername
}
// only show replies that the status owner can see
visibleToStatusOwner, err := p.filter.StatusVisible(r, requestedAccount)
visibleToStatusOwner, err := p.filter.StatusVisible(ctx, r, requestedAccount)
if err != nil || !visibleToStatusOwner {
continue
}
// only show replies that the requester can see
visibleToRequester, err := p.filter.StatusVisible(r, requestingAccount)
visibleToRequester, err := p.filter.StatusVisible(ctx, r, requestingAccount)
if err != nil || !visibleToRequester {
continue
}
@ -358,7 +358,7 @@ func (p *processor) GetFediStatusReplies(ctx context.Context, requestedUsername
replyURIs[r.ID] = rURI
}
repliesPage, err := p.tc.StatusURIsToASRepliesPage(s, onlyOtherAccounts, minID, replyURIs)
repliesPage, err := p.tc.StatusURIsToASRepliesPage(ctx, s, onlyOtherAccounts, minID, replyURIs)
if err != nil {
return nil, gtserror.NewErrorInternalError(err)
}

View file

@ -42,7 +42,7 @@ func (p *processor) FollowRequestsGet(ctx context.Context, auth *oauth.Auth) ([]
if err := p.db.GetByID(ctx, fr.AccountID, acct); err != nil {
return nil, gtserror.NewErrorInternalError(err)
}
mastoAcct, err := p.tc.AccountToMastoPublic(acct)
mastoAcct, err := p.tc.AccountToMastoPublic(ctx, acct)
if err != nil {
return nil, gtserror.NewErrorInternalError(err)
}
@ -80,7 +80,7 @@ func (p *processor) FollowRequestAccept(ctx context.Context, auth *oauth.Auth, a
return nil, gtserror.NewErrorInternalError(err)
}
r, err := p.tc.RelationshipToMasto(gtsR)
r, err := p.tc.RelationshipToMasto(ctx, gtsR)
if err != nil {
return nil, gtserror.NewErrorInternalError(err)
}

View file

@ -100,10 +100,10 @@ func (p *processor) processFromClientAPI(ctx context.Context, clientMsg gtsmodel
}
// remove any of the blocking account's statuses from the blocked account's timeline, and vice versa
if err := p.timelineManager.WipeStatusesFromAccountID(block.AccountID, block.TargetAccountID); err != nil {
if err := p.timelineManager.WipeStatusesFromAccountID(ctx, block.AccountID, block.TargetAccountID); err != nil {
return err
}
if err := p.timelineManager.WipeStatusesFromAccountID(block.TargetAccountID, block.AccountID); err != nil {
if err := p.timelineManager.WipeStatusesFromAccountID(ctx, block.TargetAccountID, block.AccountID); err != nil {
return err
}
@ -171,7 +171,7 @@ func (p *processor) processFromClientAPI(ctx context.Context, clientMsg gtsmodel
return errors.New("undo was not parseable as *gtsmodel.Status")
}
if err := p.deleteStatusFromTimelines(boost); err != nil {
if err := p.deleteStatusFromTimelines(ctx, boost); err != nil {
return err
}
@ -193,7 +193,7 @@ func (p *processor) processFromClientAPI(ctx context.Context, clientMsg gtsmodel
// delete all attachments for this status
for _, a := range statusToDelete.AttachmentIDs {
if err := p.mediaProcessor.Delete(a); err != nil {
if err := p.mediaProcessor.Delete(ctx, a); err != nil {
return err
}
}
@ -211,7 +211,7 @@ func (p *processor) processFromClientAPI(ctx context.Context, clientMsg gtsmodel
}
// delete this status from any and all timelines
if err := p.deleteStatusFromTimelines(statusToDelete); err != nil {
if err := p.deleteStatusFromTimelines(ctx, statusToDelete); err != nil {
return err
}
@ -250,7 +250,7 @@ func (p *processor) federateStatus(ctx context.Context, status *gtsmodel.Status)
return nil
}
asStatus, err := p.tc.StatusToAS(status)
asStatus, err := p.tc.StatusToAS(ctx, status)
if err != nil {
return fmt.Errorf("federateStatus: error converting status to as format: %s", err)
}
@ -278,7 +278,7 @@ func (p *processor) federateStatusDelete(ctx context.Context, status *gtsmodel.S
return nil
}
asStatus, err := p.tc.StatusToAS(status)
asStatus, err := p.tc.StatusToAS(ctx, status)
if err != nil {
return fmt.Errorf("federateStatusDelete: error converting status to as format: %s", err)
}
@ -320,9 +320,9 @@ func (p *processor) federateFollow(ctx context.Context, followRequest *gtsmodel.
return nil
}
follow := p.tc.FollowRequestToFollow(followRequest)
follow := p.tc.FollowRequestToFollow(ctx, followRequest)
asFollow, err := p.tc.FollowToAS(follow, originAccount, targetAccount)
asFollow, err := p.tc.FollowToAS(ctx, follow, originAccount, targetAccount)
if err != nil {
return fmt.Errorf("federateFollow: error converting follow to as format: %s", err)
}
@ -343,7 +343,7 @@ func (p *processor) federateUnfollow(ctx context.Context, follow *gtsmodel.Follo
}
// recreate the follow
asFollow, err := p.tc.FollowToAS(follow, originAccount, targetAccount)
asFollow, err := p.tc.FollowToAS(ctx, follow, originAccount, targetAccount)
if err != nil {
return fmt.Errorf("federateUnfollow: error converting follow to as format: %s", err)
}
@ -384,7 +384,7 @@ func (p *processor) federateUnfave(ctx context.Context, fave *gtsmodel.StatusFav
}
// create the AS fave
asFave, err := p.tc.FaveToAS(fave)
asFave, err := p.tc.FaveToAS(ctx, fave)
if err != nil {
return fmt.Errorf("federateFave: error converting fave to as format: %s", err)
}
@ -422,7 +422,7 @@ func (p *processor) federateUnannounce(ctx context.Context, boost *gtsmodel.Stat
return nil
}
asAnnounce, err := p.tc.BoostToAS(boost, originAccount, targetAccount)
asAnnounce, err := p.tc.BoostToAS(ctx, boost, originAccount, targetAccount)
if err != nil {
return fmt.Errorf("federateUnannounce: error converting status to announce: %s", err)
}
@ -458,7 +458,7 @@ func (p *processor) federateAcceptFollowRequest(ctx context.Context, follow *gts
}
// recreate the AS follow
asFollow, err := p.tc.FollowToAS(follow, originAccount, targetAccount)
asFollow, err := p.tc.FollowToAS(ctx, follow, originAccount, targetAccount)
if err != nil {
return fmt.Errorf("federateUnfollow: error converting follow to as format: %s", err)
}
@ -508,7 +508,7 @@ func (p *processor) federateFave(ctx context.Context, fave *gtsmodel.StatusFave,
}
// create the AS fave
asFave, err := p.tc.FaveToAS(fave)
asFave, err := p.tc.FaveToAS(ctx, fave)
if err != nil {
return fmt.Errorf("federateFave: error converting fave to as format: %s", err)
}
@ -522,7 +522,7 @@ func (p *processor) federateFave(ctx context.Context, fave *gtsmodel.StatusFave,
}
func (p *processor) federateAnnounce(ctx context.Context, boostWrapperStatus *gtsmodel.Status, boostingAccount *gtsmodel.Account, boostedAccount *gtsmodel.Account) error {
announce, err := p.tc.BoostToAS(boostWrapperStatus, boostingAccount, boostedAccount)
announce, err := p.tc.BoostToAS(ctx, boostWrapperStatus, boostingAccount, boostedAccount)
if err != nil {
return fmt.Errorf("federateAnnounce: error converting status to announce: %s", err)
}
@ -537,7 +537,7 @@ func (p *processor) federateAnnounce(ctx context.Context, boostWrapperStatus *gt
}
func (p *processor) federateAccountUpdate(ctx context.Context, updatedAccount *gtsmodel.Account, originAccount *gtsmodel.Account) error {
person, err := p.tc.AccountToAS(updatedAccount)
person, err := p.tc.AccountToAS(ctx, updatedAccount)
if err != nil {
return fmt.Errorf("federateAccountUpdate: error converting account to person: %s", err)
}
@ -578,7 +578,7 @@ func (p *processor) federateBlock(ctx context.Context, block *gtsmodel.Block) er
return nil
}
asBlock, err := p.tc.BlockToAS(block)
asBlock, err := p.tc.BlockToAS(ctx, block)
if err != nil {
return fmt.Errorf("federateBlock: error converting block to AS format: %s", err)
}
@ -614,7 +614,7 @@ func (p *processor) federateUnblock(ctx context.Context, block *gtsmodel.Block)
return nil
}
asBlock, err := p.tc.BlockToAS(block)
asBlock, err := p.tc.BlockToAS(ctx, block)
if err != nil {
return fmt.Errorf("federateUnblock: error converting block to AS format: %s", err)
}

View file

@ -98,7 +98,7 @@ func (p *processor) notifyStatus(ctx context.Context, status *gtsmodel.Status) e
}
// now stream the notification to the user
mastoNotif, err := p.tc.NotificationToMasto(notif)
mastoNotif, err := p.tc.NotificationToMasto(ctx, notif)
if err != nil {
return fmt.Errorf("notifyStatus: error converting notification to masto representation: %s", err)
}
@ -134,7 +134,7 @@ func (p *processor) notifyFollowRequest(ctx context.Context, followRequest *gtsm
}
// now stream the notification to the user
mastoNotif, err := p.tc.NotificationToMasto(notif)
mastoNotif, err := p.tc.NotificationToMasto(ctx, notif)
if err != nil {
return fmt.Errorf("notifyStatus: error converting notification to masto representation: %s", err)
}
@ -180,7 +180,7 @@ func (p *processor) notifyFollow(ctx context.Context, follow *gtsmodel.Follow, t
}
// now stream the notification to the user
mastoNotif, err := p.tc.NotificationToMasto(notif)
mastoNotif, err := p.tc.NotificationToMasto(ctx, notif)
if err != nil {
return fmt.Errorf("notifyStatus: error converting notification to masto representation: %s", err)
}
@ -219,7 +219,7 @@ func (p *processor) notifyFave(ctx context.Context, fave *gtsmodel.StatusFave, t
}
// now stream the notification to the user
mastoNotif, err := p.tc.NotificationToMasto(notif)
mastoNotif, err := p.tc.NotificationToMasto(ctx, notif)
if err != nil {
return fmt.Errorf("notifyStatus: error converting notification to masto representation: %s", err)
}
@ -298,7 +298,7 @@ func (p *processor) notifyAnnounce(ctx context.Context, status *gtsmodel.Status)
}
// now stream the notification to the user
mastoNotif, err := p.tc.NotificationToMasto(notif)
mastoNotif, err := p.tc.NotificationToMasto(ctx, notif)
if err != nil {
return fmt.Errorf("notifyStatus: error converting notification to masto representation: %s", err)
}
@ -377,7 +377,7 @@ func (p *processor) timelineStatusForAccount(ctx context.Context, status *gtsmod
}
// make sure the status is timelineable
timelineable, err := p.filter.StatusHometimelineable(status, timelineAccount)
timelineable, err := p.filter.StatusHometimelineable(ctx, status, timelineAccount)
if err != nil {
errors <- fmt.Errorf("timelineStatusForAccount: error getting timelineability for status for timeline with id %s: %s", accountID, err)
return
@ -388,7 +388,7 @@ func (p *processor) timelineStatusForAccount(ctx context.Context, status *gtsmod
}
// stick the status in the timeline for the account and then immediately prepare it so they can see it right away
inserted, err := p.timelineManager.IngestAndPrepare(status, timelineAccount.ID)
inserted, err := p.timelineManager.IngestAndPrepare(ctx, status, timelineAccount.ID)
if err != nil {
errors <- fmt.Errorf("timelineStatusForAccount: error ingesting status %s: %s", status.ID, err)
return
@ -396,7 +396,7 @@ func (p *processor) timelineStatusForAccount(ctx context.Context, status *gtsmod
// the status was inserted to stream it to the user
if inserted {
mastoStatus, err := p.tc.StatusToMasto(status, timelineAccount)
mastoStatus, err := p.tc.StatusToMasto(ctx, status, timelineAccount)
if err != nil {
errors <- fmt.Errorf("timelineStatusForAccount: error converting status %s to frontend representation: %s", status.ID, err)
} else {
@ -406,7 +406,7 @@ func (p *processor) timelineStatusForAccount(ctx context.Context, status *gtsmod
}
}
mastoStatus, err := p.tc.StatusToMasto(status, timelineAccount)
mastoStatus, err := p.tc.StatusToMasto(ctx, status, timelineAccount)
if err != nil {
errors <- fmt.Errorf("timelineStatusForAccount: error converting status %s to frontend representation: %s", status.ID, err)
} else {
@ -416,8 +416,8 @@ func (p *processor) timelineStatusForAccount(ctx context.Context, status *gtsmod
}
}
func (p *processor) deleteStatusFromTimelines(status *gtsmodel.Status) error {
if err := p.timelineManager.WipeStatusFromAllTimelines(status.ID); err != nil {
func (p *processor) deleteStatusFromTimelines(ctx context.Context, status *gtsmodel.Status) error {
if err := p.timelineManager.WipeStatusFromAllTimelines(ctx, status.ID); err != nil {
return err
}

View file

@ -49,7 +49,7 @@ func (p *processor) processFromFederator(ctx context.Context, federatorMsg gtsmo
return errors.New("note was not parseable as *gtsmodel.Status")
}
status, err := p.federator.EnrichRemoteStatus(federatorMsg.ReceivingAccount.Username, incomingStatus)
status, err := p.federator.EnrichRemoteStatus(ctx, federatorMsg.ReceivingAccount.Username, incomingStatus)
if err != nil {
return err
}
@ -91,7 +91,7 @@ func (p *processor) processFromFederator(ctx context.Context, federatorMsg gtsmo
return errors.New("announce was not parseable as *gtsmodel.Status")
}
if err := p.federator.DereferenceAnnounce(incomingAnnounce, federatorMsg.ReceivingAccount.Username); err != nil {
if err := p.federator.DereferenceAnnounce(ctx, incomingAnnounce, federatorMsg.ReceivingAccount.Username); err != nil {
return fmt.Errorf("error dereferencing announce from federator: %s", err)
}
@ -122,10 +122,10 @@ func (p *processor) processFromFederator(ctx context.Context, federatorMsg gtsmo
}
// remove any of the blocking account's statuses from the blocked account's timeline, and vice versa
if err := p.timelineManager.WipeStatusesFromAccountID(block.AccountID, block.TargetAccountID); err != nil {
if err := p.timelineManager.WipeStatusesFromAccountID(ctx, block.AccountID, block.TargetAccountID); err != nil {
return err
}
if err := p.timelineManager.WipeStatusesFromAccountID(block.TargetAccountID, block.AccountID); err != nil {
if err := p.timelineManager.WipeStatusesFromAccountID(ctx, block.TargetAccountID, block.AccountID); err != nil {
return err
}
// TODO: same with notifications
@ -146,7 +146,7 @@ func (p *processor) processFromFederator(ctx context.Context, federatorMsg gtsmo
return err
}
if _, _, err := p.federator.GetRemoteAccount(federatorMsg.ReceivingAccount.Username, incomingAccountURI, true); err != nil {
if _, _, err := p.federator.GetRemoteAccount(ctx, federatorMsg.ReceivingAccount.Username, incomingAccountURI, true); err != nil {
return fmt.Errorf("error dereferencing account from federator: %s", err)
}
}
@ -166,7 +166,7 @@ func (p *processor) processFromFederator(ctx context.Context, federatorMsg gtsmo
// delete all attachments for this status
for _, a := range statusToDelete.AttachmentIDs {
if err := p.mediaProcessor.Delete(a); err != nil {
if err := p.mediaProcessor.Delete(ctx, a); err != nil {
return err
}
}
@ -184,7 +184,7 @@ func (p *processor) processFromFederator(ctx context.Context, federatorMsg gtsmo
}
// remove this status from any and all timelines
return p.deleteStatusFromTimelines(statusToDelete)
return p.deleteStatusFromTimelines(ctx, statusToDelete)
case gtsmodel.ActivityStreamsProfile:
// DELETE A PROFILE/ACCOUNT
// TODO: handle side effects of account deletion here: delete all objects, statuses, media etc associated with account

View file

@ -36,7 +36,7 @@ func (p *processor) InstanceGet(ctx context.Context, domain string) (*apimodel.I
return nil, gtserror.NewErrorInternalError(fmt.Errorf("db error fetching instance %s: %s", p.config.Host, err))
}
ai, err := p.tc.InstanceToMasto(i)
ai, err := p.tc.InstanceToMasto(ctx, i)
if err != nil {
return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting instance to api representation: %s", err))
}
@ -151,7 +151,7 @@ func (p *processor) InstancePatch(ctx context.Context, form *apimodel.InstanceSe
return nil, gtserror.NewErrorInternalError(fmt.Errorf("db error updating instance %s: %s", p.config.Host, err))
}
ai, err := p.tc.InstanceToMasto(i)
ai, err := p.tc.InstanceToMasto(ctx, i)
if err != nil {
return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting instance to api representation: %s", err))
}

View file

@ -20,6 +20,7 @@ package media
import (
"bytes"
"context"
"errors"
"fmt"
"io"
@ -29,7 +30,7 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/text"
)
func (p *processor) Create(account *gtsmodel.Account, form *apimodel.AttachmentRequest) (*apimodel.Attachment, error) {
func (p *processor) Create(ctx context.Context, account *gtsmodel.Account, form *apimodel.AttachmentRequest) (*apimodel.Attachment, error) {
// open the attachment and extract the bytes from it
f, err := form.File.Open()
if err != nil {
@ -45,7 +46,7 @@ func (p *processor) Create(account *gtsmodel.Account, form *apimodel.AttachmentR
}
// allow the mediaHandler to work its magic of processing the attachment bytes, and putting them in whatever storage backend we're using
attachment, err := p.mediaHandler.ProcessAttachment(buf.Bytes(), account.ID, "")
attachment, err := p.mediaHandler.ProcessAttachment(ctx, buf.Bytes(), account.ID, "")
if err != nil {
return nil, fmt.Errorf("error reading attachment: %s", err)
}
@ -66,13 +67,13 @@ func (p *processor) Create(account *gtsmodel.Account, form *apimodel.AttachmentR
// prepare the frontend representation now -- if there are any errors here at least we can bail without
// having already put something in the database and then having to clean it up again (eugh)
mastoAttachment, err := p.tc.AttachmentToMasto(attachment)
mastoAttachment, err := p.tc.AttachmentToMasto(ctx, attachment)
if err != nil {
return nil, fmt.Errorf("error parsing media attachment to frontend type: %s", err)
}
// now we can confidently put the attachment in the database
if err := p.db.Put(attachment); err != nil {
if err := p.db.Put(ctx, attachment); err != nil {
return nil, fmt.Errorf("error storing media attachment in db: %s", err)
}

View file

@ -1,6 +1,7 @@
package media
import (
"context"
"fmt"
"strings"
@ -9,9 +10,9 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
)
func (p *processor) Delete(mediaAttachmentID string) gtserror.WithCode {
func (p *processor) Delete(ctx context.Context, mediaAttachmentID string) gtserror.WithCode {
a := &gtsmodel.MediaAttachment{}
if err := p.db.GetByID(mediaAttachmentID, a); err != nil {
if err := p.db.GetByID(ctx, mediaAttachmentID, a); err != nil {
if err == db.ErrNoEntries {
// attachment already gone
return nil
@ -37,7 +38,7 @@ func (p *processor) Delete(mediaAttachmentID string) gtserror.WithCode {
}
// delete the attachment
if err := p.db.DeleteByID(mediaAttachmentID, a); err != nil {
if err := p.db.DeleteByID(ctx, mediaAttachmentID, a); err != nil {
if err != db.ErrNoEntries {
errs = append(errs, fmt.Sprintf("remove attachment: %s", err))
}

View file

@ -19,6 +19,7 @@
package media
import (
"context"
"fmt"
"strings"
@ -28,7 +29,7 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/media"
)
func (p *processor) GetFile(account *gtsmodel.Account, form *apimodel.GetContentRequestForm) (*apimodel.Content, error) {
func (p *processor) GetFile(ctx context.Context, account *gtsmodel.Account, form *apimodel.GetContentRequestForm) (*apimodel.Content, error) {
// parse the form fields
mediaSize, err := media.ParseMediaSize(form.MediaSize)
if err != nil {
@ -48,7 +49,7 @@ func (p *processor) GetFile(account *gtsmodel.Account, form *apimodel.GetContent
// get the account that owns the media and make sure it's not suspended
acct := &gtsmodel.Account{}
if err := p.db.GetByID(form.AccountID, acct); err != nil {
if err := p.db.GetByID(ctx, form.AccountID, acct); err != nil {
return nil, gtserror.NewErrorNotFound(fmt.Errorf("account with id %s could not be selected from the db: %s", form.AccountID, err))
}
if !acct.SuspendedAt.IsZero() {
@ -57,7 +58,7 @@ func (p *processor) GetFile(account *gtsmodel.Account, form *apimodel.GetContent
// make sure the requesting account and the media account don't block each other
if account != nil {
blocked, err := p.db.IsBlocked(account.ID, form.AccountID, true)
blocked, err := p.db.IsBlocked(ctx, account.ID, form.AccountID, true)
if err != nil {
return nil, gtserror.NewErrorNotFound(fmt.Errorf("block status could not be established between accounts %s and %s: %s", form.AccountID, account.ID, err))
}
@ -73,7 +74,7 @@ func (p *processor) GetFile(account *gtsmodel.Account, form *apimodel.GetContent
switch mediaType {
case media.Emoji:
e := &gtsmodel.Emoji{}
if err := p.db.GetByID(wantedMediaID, e); err != nil {
if err := p.db.GetByID(ctx, wantedMediaID, e); err != nil {
return nil, gtserror.NewErrorNotFound(fmt.Errorf("emoji %s could not be taken from the db: %s", wantedMediaID, err))
}
if e.Disabled {
@ -91,7 +92,7 @@ func (p *processor) GetFile(account *gtsmodel.Account, form *apimodel.GetContent
}
case media.Attachment, media.Header, media.Avatar:
a := &gtsmodel.MediaAttachment{}
if err := p.db.GetByID(wantedMediaID, a); err != nil {
if err := p.db.GetByID(ctx, wantedMediaID, a); err != nil {
return nil, gtserror.NewErrorNotFound(fmt.Errorf("attachment %s could not be taken from the db: %s", wantedMediaID, err))
}
if a.AccountID != form.AccountID {

View file

@ -19,6 +19,7 @@
package media
import (
"context"
"errors"
"fmt"
@ -28,9 +29,9 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
)
func (p *processor) GetMedia(account *gtsmodel.Account, mediaAttachmentID string) (*apimodel.Attachment, gtserror.WithCode) {
func (p *processor) GetMedia(ctx context.Context, account *gtsmodel.Account, mediaAttachmentID string) (*apimodel.Attachment, gtserror.WithCode) {
attachment := &gtsmodel.MediaAttachment{}
if err := p.db.GetByID(mediaAttachmentID, attachment); err != nil {
if err := p.db.GetByID(ctx, mediaAttachmentID, attachment); err != nil {
if err == db.ErrNoEntries {
// attachment doesn't exist
return nil, gtserror.NewErrorNotFound(errors.New("attachment doesn't exist in the db"))
@ -42,7 +43,7 @@ func (p *processor) GetMedia(account *gtsmodel.Account, mediaAttachmentID string
return nil, gtserror.NewErrorNotFound(errors.New("attachment not owned by requesting account"))
}
a, err := p.tc.AttachmentToMasto(attachment)
a, err := p.tc.AttachmentToMasto(ctx, attachment)
if err != nil {
return nil, gtserror.NewErrorNotFound(fmt.Errorf("error converting attachment: %s", err))
}

View file

@ -19,6 +19,8 @@
package media
import (
"context"
"github.com/sirupsen/logrus"
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
"github.com/superseriousbusiness/gotosocial/internal/blob"
@ -33,12 +35,12 @@ import (
// Processor wraps a bunch of functions for processing media actions.
type Processor interface {
// Create creates a new media attachment belonging to the given account, using the request form.
Create(account *gtsmodel.Account, form *apimodel.AttachmentRequest) (*apimodel.Attachment, error)
Create(ctx context.Context, account *gtsmodel.Account, form *apimodel.AttachmentRequest) (*apimodel.Attachment, error)
// Delete deletes the media attachment with the given ID, including all files pertaining to that attachment.
Delete(mediaAttachmentID string) gtserror.WithCode
GetFile(account *gtsmodel.Account, form *apimodel.GetContentRequestForm) (*apimodel.Content, error)
GetMedia(account *gtsmodel.Account, mediaAttachmentID string) (*apimodel.Attachment, gtserror.WithCode)
Update(account *gtsmodel.Account, mediaAttachmentID string, form *apimodel.AttachmentUpdateRequest) (*apimodel.Attachment, gtserror.WithCode)
Delete(ctx context.Context, mediaAttachmentID string) gtserror.WithCode
GetFile(ctx context.Context, account *gtsmodel.Account, form *apimodel.GetContentRequestForm) (*apimodel.Content, error)
GetMedia(ctx context.Context, account *gtsmodel.Account, mediaAttachmentID string) (*apimodel.Attachment, gtserror.WithCode)
Update(ctx context.Context, account *gtsmodel.Account, mediaAttachmentID string, form *apimodel.AttachmentUpdateRequest) (*apimodel.Attachment, gtserror.WithCode)
}
type processor struct {

View file

@ -19,6 +19,7 @@
package media
import (
"context"
"errors"
"fmt"
@ -29,9 +30,9 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/text"
)
func (p *processor) Update(account *gtsmodel.Account, mediaAttachmentID string, form *apimodel.AttachmentUpdateRequest) (*apimodel.Attachment, gtserror.WithCode) {
func (p *processor) Update(ctx context.Context, account *gtsmodel.Account, mediaAttachmentID string, form *apimodel.AttachmentUpdateRequest) (*apimodel.Attachment, gtserror.WithCode) {
attachment := &gtsmodel.MediaAttachment{}
if err := p.db.GetByID(mediaAttachmentID, attachment); err != nil {
if err := p.db.GetByID(ctx, mediaAttachmentID, attachment); err != nil {
if err == db.ErrNoEntries {
// attachment doesn't exist
return nil, gtserror.NewErrorNotFound(errors.New("attachment doesn't exist in the db"))
@ -45,7 +46,7 @@ func (p *processor) Update(account *gtsmodel.Account, mediaAttachmentID string,
if form.Description != nil {
attachment.Description = text.RemoveHTML(*form.Description)
if err := p.db.UpdateByID(mediaAttachmentID, attachment); err != nil {
if err := p.db.UpdateByID(ctx, mediaAttachmentID, attachment); err != nil {
return nil, gtserror.NewErrorInternalError(fmt.Errorf("database error updating description: %s", err))
}
}
@ -57,12 +58,12 @@ func (p *processor) Update(account *gtsmodel.Account, mediaAttachmentID string,
}
attachment.FileMeta.Focus.X = focusx
attachment.FileMeta.Focus.Y = focusy
if err := p.db.UpdateByID(mediaAttachmentID, attachment); err != nil {
if err := p.db.UpdateByID(ctx, mediaAttachmentID, attachment); err != nil {
return nil, gtserror.NewErrorInternalError(fmt.Errorf("database error updating focus: %s", err))
}
}
a, err := p.tc.AttachmentToMasto(attachment)
a, err := p.tc.AttachmentToMasto(ctx, attachment)
if err != nil {
return nil, gtserror.NewErrorNotFound(fmt.Errorf("error converting attachment: %s", err))
}

View file

@ -19,22 +19,24 @@
package processing
import (
"context"
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
"github.com/superseriousbusiness/gotosocial/internal/oauth"
)
func (p *processor) NotificationsGet(authed *oauth.Auth, limit int, maxID string, sinceID string) ([]*apimodel.Notification, gtserror.WithCode) {
func (p *processor) NotificationsGet(ctx context.Context, authed *oauth.Auth, limit int, maxID string, sinceID string) ([]*apimodel.Notification, gtserror.WithCode) {
l := p.log.WithField("func", "NotificationsGet")
notifs, err := p.db.GetNotifications(authed.Account.ID, limit, maxID, sinceID)
notifs, err := p.db.GetNotifications(ctx, authed.Account.ID, limit, maxID, sinceID)
if err != nil {
return nil, gtserror.NewErrorInternalError(err)
}
mastoNotifs := []*apimodel.Notification{}
for _, n := range notifs {
mastoNotif, err := p.tc.NotificationToMasto(n)
mastoNotif, err := p.tc.NotificationToMasto(ctx, n)
if err != nil {
l.Debugf("got an error converting a notification to masto, will skip it: %s", err)
continue

View file

@ -51,7 +51,7 @@ import (
// for clean distribution of messages without slowing down the client API and harming the user experience.
type Processor interface {
// Start starts the Processor, reading from its channels and passing messages back and forth.
Start() error
Start(ctx context.Context) error
// Stop stops the processor cleanly, finishing handling any remaining messages before closing down.
Stop() error

View file

@ -19,6 +19,7 @@
package processing
import (
"context"
"fmt"
"net/url"
"strings"
@ -32,7 +33,7 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/util"
)
func (p *processor) SearchGet(authed *oauth.Auth, searchQuery *apimodel.SearchQuery) (*apimodel.SearchResult, gtserror.WithCode) {
func (p *processor) SearchGet(ctx context.Context, authed *oauth.Auth, searchQuery *apimodel.SearchQuery) (*apimodel.SearchResult, gtserror.WithCode) {
l := p.log.WithFields(logrus.Fields{
"func": "SearchGet",
"query": searchQuery.Query,
@ -54,7 +55,7 @@ func (p *processor) SearchGet(authed *oauth.Auth, searchQuery *apimodel.SearchQu
// check if the query is something like @whatever_username@example.org -- this means it's a remote account
if !foundOne && util.IsMention(searchQuery.Query) {
l.Debug("search term is a mention, looking it up...")
foundAccount, err := p.searchAccountByMention(authed, searchQuery.Query, searchQuery.Resolve)
foundAccount, err := p.searchAccountByMention(ctx, authed, searchQuery.Query, searchQuery.Resolve)
if err == nil && foundAccount != nil {
foundAccounts = append(foundAccounts, foundAccount)
foundOne = true
@ -65,14 +66,14 @@ func (p *processor) SearchGet(authed *oauth.Auth, searchQuery *apimodel.SearchQu
// check if the query is a URI and just do a lookup for that, straight up
if uri, err := url.Parse(query); err == nil && !foundOne {
// 1. check if it's a status
if foundStatus, err := p.searchStatusByURI(authed, uri, searchQuery.Resolve); err == nil && foundStatus != nil {
if foundStatus, err := p.searchStatusByURI(ctx, authed, uri, searchQuery.Resolve); err == nil && foundStatus != nil {
foundStatuses = append(foundStatuses, foundStatus)
foundOne = true
l.Debug("got a status by searching by URI")
}
// 2. check if it's an account
if foundAccount, err := p.searchAccountByURI(authed, uri, searchQuery.Resolve); err == nil && foundAccount != nil {
if foundAccount, err := p.searchAccountByURI(ctx, authed, uri, searchQuery.Resolve); err == nil && foundAccount != nil {
foundAccounts = append(foundAccounts, foundAccount)
foundOne = true
l.Debug("got an account by searching by URI")
@ -90,20 +91,20 @@ func (p *processor) SearchGet(authed *oauth.Auth, searchQuery *apimodel.SearchQu
*/
for _, foundAccount := range foundAccounts {
// make sure there's no block in either direction between the account and the requester
if blocked, err := p.db.IsBlocked(authed.Account.ID, foundAccount.ID, true); err == nil && !blocked {
if blocked, err := p.db.IsBlocked(ctx, authed.Account.ID, foundAccount.ID, true); err == nil && !blocked {
// all good, convert it and add it to the results
if acctMasto, err := p.tc.AccountToMastoPublic(foundAccount); err == nil && acctMasto != nil {
if acctMasto, err := p.tc.AccountToMastoPublic(ctx, foundAccount); err == nil && acctMasto != nil {
results.Accounts = append(results.Accounts, *acctMasto)
}
}
}
for _, foundStatus := range foundStatuses {
if visible, err := p.filter.StatusVisible(foundStatus, authed.Account); !visible || err != nil {
if visible, err := p.filter.StatusVisible(ctx, foundStatus, authed.Account); !visible || err != nil {
continue
}
statusMasto, err := p.tc.StatusToMasto(foundStatus, authed.Account)
statusMasto, err := p.tc.StatusToMasto(ctx, foundStatus, authed.Account)
if err != nil {
continue
}
@ -114,24 +115,24 @@ func (p *processor) SearchGet(authed *oauth.Auth, searchQuery *apimodel.SearchQu
return results, nil
}
func (p *processor) searchStatusByURI(authed *oauth.Auth, uri *url.URL, resolve bool) (*gtsmodel.Status, error) {
func (p *processor) searchStatusByURI(ctx context.Context, authed *oauth.Auth, uri *url.URL, resolve bool) (*gtsmodel.Status, error) {
l := p.log.WithFields(logrus.Fields{
"func": "searchStatusByURI",
"uri": uri.String(),
"resolve": resolve,
})
if maybeStatus, err := p.db.GetStatusByURI(uri.String()); err == nil {
if maybeStatus, err := p.db.GetStatusByURI(ctx, uri.String()); err == nil {
return maybeStatus, nil
} else if maybeStatus, err := p.db.GetStatusByURL(uri.String()); err == nil {
} else if maybeStatus, err := p.db.GetStatusByURL(ctx, uri.String()); err == nil {
return maybeStatus, nil
}
// we don't have it locally so dereference it if we're allowed to
if resolve {
status, _, _, err := p.federator.GetRemoteStatus(authed.Account.Username, uri, true)
status, _, _, err := p.federator.GetRemoteStatus(ctx, authed.Account.Username, uri, true)
if err == nil {
if err := p.federator.DereferenceRemoteThread(authed.Account.Username, uri); err != nil {
if err := p.federator.DereferenceRemoteThread(ctx, authed.Account.Username, uri); err != nil {
// try to deref the thread while we're here
l.Debugf("searchStatusByURI: error dereferencing remote thread: %s", err)
}
@ -141,16 +142,16 @@ func (p *processor) searchStatusByURI(authed *oauth.Auth, uri *url.URL, resolve
return nil, nil
}
func (p *processor) searchAccountByURI(authed *oauth.Auth, uri *url.URL, resolve bool) (*gtsmodel.Account, error) {
if maybeAccount, err := p.db.GetAccountByURI(uri.String()); err == nil {
func (p *processor) searchAccountByURI(ctx context.Context, authed *oauth.Auth, uri *url.URL, resolve bool) (*gtsmodel.Account, error) {
if maybeAccount, err := p.db.GetAccountByURI(ctx, uri.String()); err == nil {
return maybeAccount, nil
} else if maybeAccount, err := p.db.GetAccountByURL(uri.String()); err == nil {
} else if maybeAccount, err := p.db.GetAccountByURL(ctx, uri.String()); err == nil {
return maybeAccount, nil
}
if resolve {
// we don't have it locally so try and dereference it
account, _, err := p.federator.GetRemoteAccount(authed.Account.Username, uri, true)
account, _, err := p.federator.GetRemoteAccount(ctx, authed.Account.Username, uri, true)
if err != nil {
return nil, fmt.Errorf("searchAccountByURI: error dereferencing account with uri %s: %s", uri.String(), err)
}
@ -159,7 +160,7 @@ func (p *processor) searchAccountByURI(authed *oauth.Auth, uri *url.URL, resolve
return nil, nil
}
func (p *processor) searchAccountByMention(authed *oauth.Auth, mention string, resolve bool) (*gtsmodel.Account, error) {
func (p *processor) searchAccountByMention(ctx context.Context, authed *oauth.Auth, mention string, resolve bool) (*gtsmodel.Account, error) {
// query is for a remote account
username, domain, err := util.ExtractMentionParts(mention)
if err != nil {
@ -169,7 +170,7 @@ func (p *processor) searchAccountByMention(authed *oauth.Auth, mention string, r
// if it's a local account we can skip a whole bunch of stuff
maybeAcct := &gtsmodel.Account{}
if domain == p.config.Host {
maybeAcct, err = p.db.GetLocalAccountByUsername(username)
maybeAcct, err = p.db.GetLocalAccountByUsername(ctx, username)
if err != nil {
return nil, fmt.Errorf("searchAccountByMention: error getting local account by username: %s", err)
}
@ -181,7 +182,7 @@ func (p *processor) searchAccountByMention(authed *oauth.Auth, mention string, r
{Key: "username", Value: username, CaseInsensitive: true},
{Key: "domain", Value: domain, CaseInsensitive: true},
}
err = p.db.GetWhere(where, maybeAcct)
err = p.db.GetWhere(ctx, where, maybeAcct)
if err == nil {
// we've got it stored locally already!
return maybeAcct, nil
@ -197,14 +198,14 @@ func (p *processor) searchAccountByMention(authed *oauth.Auth, mention string, r
// we're allowed to resolve it so let's try
// first we need to webfinger the remote account to convert the username and domain into the activitypub URI for the account
acctURI, err := p.federator.FingerRemoteAccount(authed.Account.Username, username, domain)
acctURI, err := p.federator.FingerRemoteAccount(ctx, authed.Account.Username, username, domain)
if err != nil {
// something went wrong doing the webfinger lookup so we can't process the request
return nil, fmt.Errorf("searchAccountByMention: error fingering remote account with username %s and domain %s: %s", username, domain, err)
}
// we don't have it locally so try and dereference it
account, _, err := p.federator.GetRemoteAccount(authed.Account.Username, acctURI, true)
account, _, err := p.federator.GetRemoteAccount(ctx, authed.Account.Username, acctURI, true)
if err != nil {
return nil, fmt.Errorf("searchAccountByMention: error dereferencing account with uri %s: %s", acctURI.String(), err)
}

View file

@ -19,47 +19,49 @@
package processing
import (
"context"
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
"github.com/superseriousbusiness/gotosocial/internal/oauth"
)
func (p *processor) StatusCreate(authed *oauth.Auth, form *apimodel.AdvancedStatusCreateForm) (*apimodel.Status, error) {
return p.statusProcessor.Create(authed.Account, authed.Application, form)
func (p *processor) StatusCreate(ctx context.Context, authed *oauth.Auth, form *apimodel.AdvancedStatusCreateForm) (*apimodel.Status, error) {
return p.statusProcessor.Create(ctx, authed.Account, authed.Application, form)
}
func (p *processor) StatusDelete(authed *oauth.Auth, targetStatusID string) (*apimodel.Status, error) {
return p.statusProcessor.Delete(authed.Account, targetStatusID)
func (p *processor) StatusDelete(ctx context.Context, authed *oauth.Auth, targetStatusID string) (*apimodel.Status, error) {
return p.statusProcessor.Delete(ctx, authed.Account, targetStatusID)
}
func (p *processor) StatusFave(authed *oauth.Auth, targetStatusID string) (*apimodel.Status, error) {
return p.statusProcessor.Fave(authed.Account, targetStatusID)
func (p *processor) StatusFave(ctx context.Context, authed *oauth.Auth, targetStatusID string) (*apimodel.Status, error) {
return p.statusProcessor.Fave(ctx, authed.Account, targetStatusID)
}
func (p *processor) StatusBoost(authed *oauth.Auth, targetStatusID string) (*apimodel.Status, gtserror.WithCode) {
return p.statusProcessor.Boost(authed.Account, authed.Application, targetStatusID)
func (p *processor) StatusBoost(ctx context.Context, authed *oauth.Auth, targetStatusID string) (*apimodel.Status, gtserror.WithCode) {
return p.statusProcessor.Boost(ctx, authed.Account, authed.Application, targetStatusID)
}
func (p *processor) StatusUnboost(authed *oauth.Auth, targetStatusID string) (*apimodel.Status, gtserror.WithCode) {
return p.statusProcessor.Unboost(authed.Account, authed.Application, targetStatusID)
func (p *processor) StatusUnboost(ctx context.Context, authed *oauth.Auth, targetStatusID string) (*apimodel.Status, gtserror.WithCode) {
return p.statusProcessor.Unboost(ctx, authed.Account, authed.Application, targetStatusID)
}
func (p *processor) StatusBoostedBy(authed *oauth.Auth, targetStatusID string) ([]*apimodel.Account, gtserror.WithCode) {
return p.statusProcessor.BoostedBy(authed.Account, targetStatusID)
func (p *processor) StatusBoostedBy(ctx context.Context, authed *oauth.Auth, targetStatusID string) ([]*apimodel.Account, gtserror.WithCode) {
return p.statusProcessor.BoostedBy(ctx, authed.Account, targetStatusID)
}
func (p *processor) StatusFavedBy(authed *oauth.Auth, targetStatusID string) ([]*apimodel.Account, error) {
return p.statusProcessor.FavedBy(authed.Account, targetStatusID)
func (p *processor) StatusFavedBy(ctx context.Context, authed *oauth.Auth, targetStatusID string) ([]*apimodel.Account, error) {
return p.statusProcessor.FavedBy(ctx, authed.Account, targetStatusID)
}
func (p *processor) StatusGet(authed *oauth.Auth, targetStatusID string) (*apimodel.Status, error) {
return p.statusProcessor.Get(authed.Account, targetStatusID)
func (p *processor) StatusGet(ctx context.Context, authed *oauth.Auth, targetStatusID string) (*apimodel.Status, error) {
return p.statusProcessor.Get(ctx, authed.Account, targetStatusID)
}
func (p *processor) StatusUnfave(authed *oauth.Auth, targetStatusID string) (*apimodel.Status, error) {
return p.statusProcessor.Unfave(authed.Account, targetStatusID)
func (p *processor) StatusUnfave(ctx context.Context, authed *oauth.Auth, targetStatusID string) (*apimodel.Status, error) {
return p.statusProcessor.Unfave(ctx, authed.Account, targetStatusID)
}
func (p *processor) StatusGetContext(authed *oauth.Auth, targetStatusID string) (*apimodel.Context, gtserror.WithCode) {
return p.statusProcessor.Context(authed.Account, targetStatusID)
func (p *processor) StatusGetContext(ctx context.Context, authed *oauth.Auth, targetStatusID string) (*apimodel.Context, gtserror.WithCode) {
return p.statusProcessor.Context(ctx, authed.Account, targetStatusID)
}

View file

@ -1,6 +1,25 @@
/*
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 status
import (
"context"
"errors"
"fmt"
@ -9,8 +28,8 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
)
func (p *processor) Boost(requestingAccount *gtsmodel.Account, application *gtsmodel.Application, targetStatusID string) (*apimodel.Status, gtserror.WithCode) {
targetStatus, err := p.db.GetStatusByID(targetStatusID)
func (p *processor) Boost(ctx context.Context, requestingAccount *gtsmodel.Account, application *gtsmodel.Application, targetStatusID string) (*apimodel.Status, gtserror.WithCode) {
targetStatus, err := p.db.GetStatusByID(ctx, targetStatusID)
if err != nil {
return nil, gtserror.NewErrorNotFound(fmt.Errorf("error fetching status %s: %s", targetStatusID, err))
}
@ -18,7 +37,7 @@ func (p *processor) Boost(requestingAccount *gtsmodel.Account, application *gtsm
return nil, gtserror.NewErrorNotFound(fmt.Errorf("no status owner for status %s", targetStatusID))
}
visible, err := p.filter.StatusVisible(targetStatus, requestingAccount)
visible, err := p.filter.StatusVisible(ctx, targetStatus, requestingAccount)
if err != nil {
return nil, gtserror.NewErrorNotFound(fmt.Errorf("error seeing if status %s is visible: %s", targetStatus.ID, err))
}
@ -32,7 +51,7 @@ func (p *processor) Boost(requestingAccount *gtsmodel.Account, application *gtsm
}
// it's visible! it's boostable! so let's boost the FUCK out of it
boostWrapperStatus, err := p.tc.StatusToBoost(targetStatus, requestingAccount)
boostWrapperStatus, err := p.tc.StatusToBoost(ctx, targetStatus, requestingAccount)
if err != nil {
return nil, gtserror.NewErrorInternalError(err)
}
@ -41,7 +60,7 @@ func (p *processor) Boost(requestingAccount *gtsmodel.Account, application *gtsm
boostWrapperStatus.BoostOfAccount = targetStatus.Account
// put the boost in the database
if err := p.db.PutStatus(boostWrapperStatus); err != nil {
if err := p.db.PutStatus(ctx, boostWrapperStatus); err != nil {
return nil, gtserror.NewErrorInternalError(err)
}
@ -55,7 +74,7 @@ func (p *processor) Boost(requestingAccount *gtsmodel.Account, application *gtsm
}
// return the frontend representation of the new status to the submitter
mastoStatus, err := p.tc.StatusToMasto(boostWrapperStatus, requestingAccount)
mastoStatus, err := p.tc.StatusToMasto(ctx, boostWrapperStatus, requestingAccount)
if err != nil {
return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting status %s to frontend representation: %s", targetStatus.ID, err))
}

View file

@ -1,6 +1,25 @@
/*
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 status
import (
"context"
"errors"
"fmt"
@ -9,8 +28,8 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
)
func (p *processor) BoostedBy(requestingAccount *gtsmodel.Account, targetStatusID string) ([]*apimodel.Account, gtserror.WithCode) {
targetStatus, err := p.db.GetStatusByID(targetStatusID)
func (p *processor) BoostedBy(ctx context.Context, requestingAccount *gtsmodel.Account, targetStatusID string) ([]*apimodel.Account, gtserror.WithCode) {
targetStatus, err := p.db.GetStatusByID(ctx, targetStatusID)
if err != nil {
return nil, gtserror.NewErrorNotFound(fmt.Errorf("error fetching status %s: %s", targetStatusID, err))
}
@ -18,7 +37,7 @@ func (p *processor) BoostedBy(requestingAccount *gtsmodel.Account, targetStatusI
return nil, gtserror.NewErrorNotFound(fmt.Errorf("no status owner for status %s", targetStatusID))
}
visible, err := p.filter.StatusVisible(targetStatus, requestingAccount)
visible, err := p.filter.StatusVisible(ctx, targetStatus, requestingAccount)
if err != nil {
return nil, gtserror.NewErrorNotFound(fmt.Errorf("error seeing if status %s is visible: %s", targetStatus.ID, err))
}
@ -26,7 +45,7 @@ func (p *processor) BoostedBy(requestingAccount *gtsmodel.Account, targetStatusI
return nil, gtserror.NewErrorNotFound(errors.New("status is not visible"))
}
statusReblogs, err := p.db.GetStatusReblogs(targetStatus)
statusReblogs, err := p.db.GetStatusReblogs(ctx, targetStatus)
if err != nil {
return nil, gtserror.NewErrorNotFound(fmt.Errorf("StatusBoostedBy: error seeing who boosted status: %s", err))
}
@ -34,7 +53,7 @@ func (p *processor) BoostedBy(requestingAccount *gtsmodel.Account, targetStatusI
// filter the list so the user doesn't see accounts they blocked or which blocked them
filteredAccounts := []*gtsmodel.Account{}
for _, s := range statusReblogs {
blocked, err := p.db.IsBlocked(requestingAccount.ID, s.AccountID, true)
blocked, err := p.db.IsBlocked(ctx, requestingAccount.ID, s.AccountID, true)
if err != nil {
return nil, gtserror.NewErrorNotFound(fmt.Errorf("StatusBoostedBy: error checking blocks: %s", err))
}
@ -48,7 +67,7 @@ func (p *processor) BoostedBy(requestingAccount *gtsmodel.Account, targetStatusI
// now we can return the masto representation of those accounts
mastoAccounts := []*apimodel.Account{}
for _, acc := range filteredAccounts {
mastoAccount, err := p.tc.AccountToMastoPublic(acc)
mastoAccount, err := p.tc.AccountToMastoPublic(ctx, acc)
if err != nil {
return nil, gtserror.NewErrorNotFound(fmt.Errorf("StatusFavedBy: error converting account to api model: %s", err))
}

View file

@ -1,6 +1,25 @@
/*
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 status
import (
"context"
"errors"
"fmt"
"sort"
@ -10,8 +29,8 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
)
func (p *processor) Context(requestingAccount *gtsmodel.Account, targetStatusID string) (*apimodel.Context, gtserror.WithCode) {
targetStatus, err := p.db.GetStatusByID(targetStatusID)
func (p *processor) Context(ctx context.Context, requestingAccount *gtsmodel.Account, targetStatusID string) (*apimodel.Context, gtserror.WithCode) {
targetStatus, err := p.db.GetStatusByID(ctx, targetStatusID)
if err != nil {
return nil, gtserror.NewErrorNotFound(fmt.Errorf("error fetching status %s: %s", targetStatusID, err))
}
@ -19,7 +38,7 @@ func (p *processor) Context(requestingAccount *gtsmodel.Account, targetStatusID
return nil, gtserror.NewErrorNotFound(fmt.Errorf("no status owner for status %s", targetStatusID))
}
visible, err := p.filter.StatusVisible(targetStatus, requestingAccount)
visible, err := p.filter.StatusVisible(ctx, targetStatus, requestingAccount)
if err != nil {
return nil, gtserror.NewErrorNotFound(fmt.Errorf("error seeing if status %s is visible: %s", targetStatus.ID, err))
}
@ -32,14 +51,14 @@ func (p *processor) Context(requestingAccount *gtsmodel.Account, targetStatusID
Descendants: []apimodel.Status{},
}
parents, err := p.db.GetStatusParents(targetStatus, false)
parents, err := p.db.GetStatusParents(ctx, targetStatus, false)
if err != nil {
return nil, gtserror.NewErrorInternalError(err)
}
for _, status := range parents {
if v, err := p.filter.StatusVisible(status, requestingAccount); err == nil && v {
mastoStatus, err := p.tc.StatusToMasto(status, requestingAccount)
if v, err := p.filter.StatusVisible(ctx, status, requestingAccount); err == nil && v {
mastoStatus, err := p.tc.StatusToMasto(ctx, status, requestingAccount)
if err == nil {
context.Ancestors = append(context.Ancestors, *mastoStatus)
}
@ -50,14 +69,14 @@ func (p *processor) Context(requestingAccount *gtsmodel.Account, targetStatusID
return context.Ancestors[i].ID < context.Ancestors[j].ID
})
children, err := p.db.GetStatusChildren(targetStatus, false, "")
children, err := p.db.GetStatusChildren(ctx, targetStatus, false, "")
if err != nil {
return nil, gtserror.NewErrorInternalError(err)
}
for _, status := range children {
if v, err := p.filter.StatusVisible(status, requestingAccount); err == nil && v {
mastoStatus, err := p.tc.StatusToMasto(status, requestingAccount)
if v, err := p.filter.StatusVisible(ctx, status, requestingAccount); err == nil && v {
mastoStatus, err := p.tc.StatusToMasto(ctx, status, requestingAccount)
if err == nil {
context.Descendants = append(context.Descendants, *mastoStatus)
}

View file

@ -1,6 +1,25 @@
/*
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 status
import (
"context"
"fmt"
"time"
@ -12,7 +31,7 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/util"
)
func (p *processor) Create(account *gtsmodel.Account, application *gtsmodel.Application, form *apimodel.AdvancedStatusCreateForm) (*apimodel.Status, gtserror.WithCode) {
func (p *processor) Create(ctx context.Context, account *gtsmodel.Account, application *gtsmodel.Application, form *apimodel.AdvancedStatusCreateForm) (*apimodel.Status, gtserror.WithCode) {
uris := util.GenerateURIsForAccount(account.Username, p.config.Protocol, p.config.Host)
thisStatusID, err := id.NewULID()
if err != nil {
@ -38,40 +57,40 @@ func (p *processor) Create(account *gtsmodel.Account, application *gtsmodel.Appl
Text: form.Status,
}
if err := p.ProcessReplyToID(form, account.ID, newStatus); err != nil {
if err := p.ProcessReplyToID(ctx, form, account.ID, newStatus); err != nil {
return nil, gtserror.NewErrorInternalError(err)
}
if err := p.ProcessMediaIDs(form, account.ID, newStatus); err != nil {
if err := p.ProcessMediaIDs(ctx, form, account.ID, newStatus); err != nil {
return nil, gtserror.NewErrorInternalError(err)
}
if err := p.ProcessVisibility(form, account.Privacy, newStatus); err != nil {
if err := p.ProcessVisibility(ctx, form, account.Privacy, newStatus); err != nil {
return nil, gtserror.NewErrorInternalError(err)
}
if err := p.ProcessLanguage(form, account.Language, newStatus); err != nil {
if err := p.ProcessLanguage(ctx, form, account.Language, newStatus); err != nil {
return nil, gtserror.NewErrorInternalError(err)
}
if err := p.ProcessMentions(form, account.ID, newStatus); err != nil {
if err := p.ProcessMentions(ctx, form, account.ID, newStatus); err != nil {
return nil, gtserror.NewErrorInternalError(err)
}
if err := p.ProcessTags(form, account.ID, newStatus); err != nil {
if err := p.ProcessTags(ctx, form, account.ID, newStatus); err != nil {
return nil, gtserror.NewErrorInternalError(err)
}
if err := p.ProcessEmojis(form, account.ID, newStatus); err != nil {
if err := p.ProcessEmojis(ctx, form, account.ID, newStatus); err != nil {
return nil, gtserror.NewErrorInternalError(err)
}
if err := p.ProcessContent(form, account.ID, newStatus); err != nil {
if err := p.ProcessContent(ctx, form, account.ID, newStatus); err != nil {
return nil, gtserror.NewErrorInternalError(err)
}
// put the new status in the database
if err := p.db.PutStatus(newStatus); err != nil {
if err := p.db.PutStatus(ctx, newStatus); err != nil {
return nil, gtserror.NewErrorInternalError(err)
}
@ -84,7 +103,7 @@ func (p *processor) Create(account *gtsmodel.Account, application *gtsmodel.Appl
}
// return the frontend representation of the new status to the submitter
mastoStatus, err := p.tc.StatusToMasto(newStatus, account)
mastoStatus, err := p.tc.StatusToMasto(ctx, newStatus, account)
if err != nil {
return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting status %s to frontend representation: %s", newStatus.ID, err))
}

View file

@ -1,6 +1,25 @@
/*
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 status
import (
"context"
"errors"
"fmt"
@ -9,8 +28,8 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
)
func (p *processor) Delete(requestingAccount *gtsmodel.Account, targetStatusID string) (*apimodel.Status, gtserror.WithCode) {
targetStatus, err := p.db.GetStatusByID(targetStatusID)
func (p *processor) Delete(ctx context.Context, requestingAccount *gtsmodel.Account, targetStatusID string) (*apimodel.Status, gtserror.WithCode) {
targetStatus, err := p.db.GetStatusByID(ctx, targetStatusID)
if err != nil {
return nil, gtserror.NewErrorNotFound(fmt.Errorf("error fetching status %s: %s", targetStatusID, err))
}
@ -22,12 +41,12 @@ func (p *processor) Delete(requestingAccount *gtsmodel.Account, targetStatusID s
return nil, gtserror.NewErrorForbidden(errors.New("status doesn't belong to requesting account"))
}
mastoStatus, err := p.tc.StatusToMasto(targetStatus, requestingAccount)
mastoStatus, err := p.tc.StatusToMasto(ctx, targetStatus, requestingAccount)
if err != nil {
return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting status %s to frontend representation: %s", targetStatus.ID, err))
}
if err := p.db.DeleteByID(targetStatus.ID, &gtsmodel.Status{}); err != nil {
if err := p.db.DeleteByID(ctx, targetStatus.ID, &gtsmodel.Status{}); err != nil {
return nil, gtserror.NewErrorInternalError(fmt.Errorf("error deleting status from the database: %s", err))
}

View file

@ -1,6 +1,25 @@
/*
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 status
import (
"context"
"errors"
"fmt"
@ -12,8 +31,8 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/util"
)
func (p *processor) Fave(requestingAccount *gtsmodel.Account, targetStatusID string) (*apimodel.Status, gtserror.WithCode) {
targetStatus, err := p.db.GetStatusByID(targetStatusID)
func (p *processor) Fave(ctx context.Context, requestingAccount *gtsmodel.Account, targetStatusID string) (*apimodel.Status, gtserror.WithCode) {
targetStatus, err := p.db.GetStatusByID(ctx, targetStatusID)
if err != nil {
return nil, gtserror.NewErrorNotFound(fmt.Errorf("error fetching status %s: %s", targetStatusID, err))
}
@ -21,7 +40,7 @@ func (p *processor) Fave(requestingAccount *gtsmodel.Account, targetStatusID str
return nil, gtserror.NewErrorNotFound(fmt.Errorf("no status owner for status %s", targetStatusID))
}
visible, err := p.filter.StatusVisible(targetStatus, requestingAccount)
visible, err := p.filter.StatusVisible(ctx, targetStatus, requestingAccount)
if err != nil {
return nil, gtserror.NewErrorNotFound(fmt.Errorf("error seeing if status %s is visible: %s", targetStatus.ID, err))
}
@ -37,7 +56,7 @@ func (p *processor) Fave(requestingAccount *gtsmodel.Account, targetStatusID str
// first check if the status is already faved, if so we don't need to do anything
newFave := true
gtsFave := &gtsmodel.StatusFave{}
if err := p.db.GetWhere([]db.Where{{Key: "status_id", Value: targetStatus.ID}, {Key: "account_id", Value: requestingAccount.ID}}, gtsFave); err == nil {
if err := p.db.GetWhere(ctx, []db.Where{{Key: "status_id", Value: targetStatus.ID}, {Key: "account_id", Value: requestingAccount.ID}}, gtsFave); err == nil {
// we already have a fave for this status
newFave = false
}
@ -60,7 +79,7 @@ func (p *processor) Fave(requestingAccount *gtsmodel.Account, targetStatusID str
URI: util.GenerateURIForLike(requestingAccount.Username, p.config.Protocol, p.config.Host, thisFaveID),
}
if err := p.db.Put(gtsFave); err != nil {
if err := p.db.Put(ctx, gtsFave); err != nil {
return nil, gtserror.NewErrorInternalError(fmt.Errorf("error putting fave in database: %s", err))
}
@ -75,7 +94,7 @@ func (p *processor) Fave(requestingAccount *gtsmodel.Account, targetStatusID str
}
// return the mastodon representation of the target status
mastoStatus, err := p.tc.StatusToMasto(targetStatus, requestingAccount)
mastoStatus, err := p.tc.StatusToMasto(ctx, targetStatus, requestingAccount)
if err != nil {
return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting status %s to frontend representation: %s", targetStatus.ID, err))
}

View file

@ -1,6 +1,25 @@
/*
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 status
import (
"context"
"errors"
"fmt"
@ -9,8 +28,8 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
)
func (p *processor) FavedBy(requestingAccount *gtsmodel.Account, targetStatusID string) ([]*apimodel.Account, gtserror.WithCode) {
targetStatus, err := p.db.GetStatusByID(targetStatusID)
func (p *processor) FavedBy(ctx context.Context, requestingAccount *gtsmodel.Account, targetStatusID string) ([]*apimodel.Account, gtserror.WithCode) {
targetStatus, err := p.db.GetStatusByID(ctx, targetStatusID)
if err != nil {
return nil, gtserror.NewErrorNotFound(fmt.Errorf("error fetching status %s: %s", targetStatusID, err))
}
@ -18,7 +37,7 @@ func (p *processor) FavedBy(requestingAccount *gtsmodel.Account, targetStatusID
return nil, gtserror.NewErrorNotFound(fmt.Errorf("no status owner for status %s", targetStatusID))
}
visible, err := p.filter.StatusVisible(targetStatus, requestingAccount)
visible, err := p.filter.StatusVisible(ctx, targetStatus, requestingAccount)
if err != nil {
return nil, gtserror.NewErrorNotFound(fmt.Errorf("error seeing if status %s is visible: %s", targetStatus.ID, err))
}
@ -26,7 +45,7 @@ func (p *processor) FavedBy(requestingAccount *gtsmodel.Account, targetStatusID
return nil, gtserror.NewErrorNotFound(errors.New("status is not visible"))
}
statusFaves, err := p.db.GetStatusFaves(targetStatus)
statusFaves, err := p.db.GetStatusFaves(ctx, targetStatus)
if err != nil {
return nil, gtserror.NewErrorNotFound(fmt.Errorf("error seeing who faved status: %s", err))
}
@ -34,7 +53,7 @@ func (p *processor) FavedBy(requestingAccount *gtsmodel.Account, targetStatusID
// filter the list so the user doesn't see accounts they blocked or which blocked them
filteredAccounts := []*gtsmodel.Account{}
for _, fave := range statusFaves {
blocked, err := p.db.IsBlocked(requestingAccount.ID, fave.AccountID, true)
blocked, err := p.db.IsBlocked(ctx, requestingAccount.ID, fave.AccountID, true)
if err != nil {
return nil, gtserror.NewErrorInternalError(fmt.Errorf("error checking blocks: %s", err))
}
@ -46,7 +65,7 @@ func (p *processor) FavedBy(requestingAccount *gtsmodel.Account, targetStatusID
// now we can return the masto representation of those accounts
mastoAccounts := []*apimodel.Account{}
for _, acc := range filteredAccounts {
mastoAccount, err := p.tc.AccountToMastoPublic(acc)
mastoAccount, err := p.tc.AccountToMastoPublic(ctx, acc)
if err != nil {
return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting status %s to frontend representation: %s", targetStatus.ID, err))
}

View file

@ -1,6 +1,25 @@
/*
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 status
import (
"context"
"errors"
"fmt"
@ -9,8 +28,8 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
)
func (p *processor) Get(requestingAccount *gtsmodel.Account, targetStatusID string) (*apimodel.Status, gtserror.WithCode) {
targetStatus, err := p.db.GetStatusByID(targetStatusID)
func (p *processor) Get(ctx context.Context, requestingAccount *gtsmodel.Account, targetStatusID string) (*apimodel.Status, gtserror.WithCode) {
targetStatus, err := p.db.GetStatusByID(ctx, targetStatusID)
if err != nil {
return nil, gtserror.NewErrorNotFound(fmt.Errorf("error fetching status %s: %s", targetStatusID, err))
}
@ -18,7 +37,7 @@ func (p *processor) Get(requestingAccount *gtsmodel.Account, targetStatusID stri
return nil, gtserror.NewErrorNotFound(fmt.Errorf("no status owner for status %s", targetStatusID))
}
visible, err := p.filter.StatusVisible(targetStatus, requestingAccount)
visible, err := p.filter.StatusVisible(ctx, targetStatus, requestingAccount)
if err != nil {
return nil, gtserror.NewErrorNotFound(fmt.Errorf("error seeing if status %s is visible: %s", targetStatus.ID, err))
}
@ -26,7 +45,7 @@ func (p *processor) Get(requestingAccount *gtsmodel.Account, targetStatusID stri
return nil, gtserror.NewErrorNotFound(errors.New("status is not visible"))
}
mastoStatus, err := p.tc.StatusToMasto(targetStatus, requestingAccount)
mastoStatus, err := p.tc.StatusToMasto(ctx, targetStatus, requestingAccount)
if err != nil {
return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting status %s to frontend representation: %s", targetStatus.ID, err))
}

View file

@ -1,6 +1,26 @@
/*
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 status
import (
"context"
"github.com/sirupsen/logrus"
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
"github.com/superseriousbusiness/gotosocial/internal/config"
@ -15,38 +35,38 @@ import (
// Processor wraps a bunch of functions for processing statuses.
type Processor interface {
// Create processes the given form to create a new status, returning the api model representation of that status if it's OK.
Create(account *gtsmodel.Account, application *gtsmodel.Application, form *apimodel.AdvancedStatusCreateForm) (*apimodel.Status, gtserror.WithCode)
Create(ctx context.Context, account *gtsmodel.Account, application *gtsmodel.Application, form *apimodel.AdvancedStatusCreateForm) (*apimodel.Status, gtserror.WithCode)
// Delete processes the delete of a given status, returning the deleted status if the delete goes through.
Delete(account *gtsmodel.Account, targetStatusID string) (*apimodel.Status, gtserror.WithCode)
Delete(ctx context.Context, account *gtsmodel.Account, targetStatusID string) (*apimodel.Status, gtserror.WithCode)
// Fave processes the faving of a given status, returning the updated status if the fave goes through.
Fave(account *gtsmodel.Account, targetStatusID string) (*apimodel.Status, gtserror.WithCode)
Fave(ctx context.Context, account *gtsmodel.Account, targetStatusID string) (*apimodel.Status, gtserror.WithCode)
// Boost processes the boost/reblog of a given status, returning the newly-created boost if all is well.
Boost(account *gtsmodel.Account, application *gtsmodel.Application, targetStatusID string) (*apimodel.Status, gtserror.WithCode)
Boost(ctx context.Context, account *gtsmodel.Account, application *gtsmodel.Application, targetStatusID string) (*apimodel.Status, gtserror.WithCode)
// Unboost processes the unboost/unreblog of a given status, returning the status if all is well.
Unboost(account *gtsmodel.Account, application *gtsmodel.Application, targetStatusID string) (*apimodel.Status, gtserror.WithCode)
Unboost(ctx context.Context, account *gtsmodel.Account, application *gtsmodel.Application, targetStatusID string) (*apimodel.Status, gtserror.WithCode)
// BoostedBy returns a slice of accounts that have boosted the given status, filtered according to privacy settings.
BoostedBy(account *gtsmodel.Account, targetStatusID string) ([]*apimodel.Account, gtserror.WithCode)
BoostedBy(ctx context.Context, account *gtsmodel.Account, targetStatusID string) ([]*apimodel.Account, gtserror.WithCode)
// FavedBy returns a slice of accounts that have liked the given status, filtered according to privacy settings.
FavedBy(account *gtsmodel.Account, targetStatusID string) ([]*apimodel.Account, gtserror.WithCode)
FavedBy(ctx context.Context, account *gtsmodel.Account, targetStatusID string) ([]*apimodel.Account, gtserror.WithCode)
// Get gets the given status, taking account of privacy settings and blocks etc.
Get(account *gtsmodel.Account, targetStatusID string) (*apimodel.Status, gtserror.WithCode)
Get(ctx context.Context, account *gtsmodel.Account, targetStatusID string) (*apimodel.Status, gtserror.WithCode)
// Unfave processes the unfaving of a given status, returning the updated status if the fave goes through.
Unfave(account *gtsmodel.Account, targetStatusID string) (*apimodel.Status, gtserror.WithCode)
Unfave(ctx context.Context, account *gtsmodel.Account, targetStatusID string) (*apimodel.Status, gtserror.WithCode)
// Context returns the context (previous and following posts) from the given status ID
Context(account *gtsmodel.Account, targetStatusID string) (*apimodel.Context, gtserror.WithCode)
Context(ctx context.Context, account *gtsmodel.Account, targetStatusID string) (*apimodel.Context, gtserror.WithCode)
/*
PROCESSING UTILS
*/
ProcessVisibility(form *apimodel.AdvancedStatusCreateForm, accountDefaultVis gtsmodel.Visibility, status *gtsmodel.Status) error
ProcessReplyToID(form *apimodel.AdvancedStatusCreateForm, thisAccountID string, status *gtsmodel.Status) error
ProcessMediaIDs(form *apimodel.AdvancedStatusCreateForm, thisAccountID string, status *gtsmodel.Status) error
ProcessLanguage(form *apimodel.AdvancedStatusCreateForm, accountDefaultLanguage string, status *gtsmodel.Status) error
ProcessMentions(form *apimodel.AdvancedStatusCreateForm, accountID string, status *gtsmodel.Status) error
ProcessTags(form *apimodel.AdvancedStatusCreateForm, accountID string, status *gtsmodel.Status) error
ProcessEmojis(form *apimodel.AdvancedStatusCreateForm, accountID string, status *gtsmodel.Status) error
ProcessContent(form *apimodel.AdvancedStatusCreateForm, accountID string, status *gtsmodel.Status) error
ProcessVisibility(ctx context.Context, form *apimodel.AdvancedStatusCreateForm, accountDefaultVis gtsmodel.Visibility, status *gtsmodel.Status) error
ProcessReplyToID(ctx context.Context, form *apimodel.AdvancedStatusCreateForm, thisAccountID string, status *gtsmodel.Status) error
ProcessMediaIDs(ctx context.Context, form *apimodel.AdvancedStatusCreateForm, thisAccountID string, status *gtsmodel.Status) error
ProcessLanguage(ctx context.Context, form *apimodel.AdvancedStatusCreateForm, accountDefaultLanguage string, status *gtsmodel.Status) error
ProcessMentions(ctx context.Context, form *apimodel.AdvancedStatusCreateForm, accountID string, status *gtsmodel.Status) error
ProcessTags(ctx context.Context, form *apimodel.AdvancedStatusCreateForm, accountID string, status *gtsmodel.Status) error
ProcessEmojis(ctx context.Context, form *apimodel.AdvancedStatusCreateForm, accountID string, status *gtsmodel.Status) error
ProcessContent(ctx context.Context, form *apimodel.AdvancedStatusCreateForm, accountID string, status *gtsmodel.Status) error
}
type processor struct {

View file

@ -1,6 +1,25 @@
/*
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 status
import (
"context"
"errors"
"fmt"
@ -10,8 +29,8 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
)
func (p *processor) Unboost(requestingAccount *gtsmodel.Account, application *gtsmodel.Application, targetStatusID string) (*apimodel.Status, gtserror.WithCode) {
targetStatus, err := p.db.GetStatusByID(targetStatusID)
func (p *processor) Unboost(ctx context.Context, requestingAccount *gtsmodel.Account, application *gtsmodel.Application, targetStatusID string) (*apimodel.Status, gtserror.WithCode) {
targetStatus, err := p.db.GetStatusByID(ctx, targetStatusID)
if err != nil {
return nil, gtserror.NewErrorNotFound(fmt.Errorf("error fetching status %s: %s", targetStatusID, err))
}
@ -19,7 +38,7 @@ func (p *processor) Unboost(requestingAccount *gtsmodel.Account, application *gt
return nil, gtserror.NewErrorNotFound(fmt.Errorf("no status owner for status %s", targetStatusID))
}
visible, err := p.filter.StatusVisible(targetStatus, requestingAccount)
visible, err := p.filter.StatusVisible(ctx, targetStatus, requestingAccount)
if err != nil {
return nil, gtserror.NewErrorNotFound(fmt.Errorf("error seeing if status %s is visible: %s", targetStatus.ID, err))
}
@ -41,7 +60,7 @@ func (p *processor) Unboost(requestingAccount *gtsmodel.Account, application *gt
Value: requestingAccount.ID,
},
}
err = p.db.GetWhere(where, gtsBoost)
err = p.db.GetWhere(ctx, where, gtsBoost)
if err == nil {
// we have a boost
toUnboost = true
@ -58,7 +77,7 @@ func (p *processor) Unboost(requestingAccount *gtsmodel.Account, application *gt
if toUnboost {
// we had a boost, so take some action to get rid of it
if err := p.db.DeleteWhere(where, &gtsmodel.Status{}); err != nil {
if err := p.db.DeleteWhere(ctx, where, &gtsmodel.Status{}); err != nil {
return nil, gtserror.NewErrorInternalError(fmt.Errorf("error unboosting status: %s", err))
}
@ -79,7 +98,7 @@ func (p *processor) Unboost(requestingAccount *gtsmodel.Account, application *gt
}
}
mastoStatus, err := p.tc.StatusToMasto(targetStatus, requestingAccount)
mastoStatus, err := p.tc.StatusToMasto(ctx, targetStatus, requestingAccount)
if err != nil {
return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting status %s to frontend representation: %s", targetStatus.ID, err))
}

View file

@ -1,6 +1,25 @@
/*
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 status
import (
"context"
"errors"
"fmt"
@ -10,8 +29,8 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
)
func (p *processor) Unfave(requestingAccount *gtsmodel.Account, targetStatusID string) (*apimodel.Status, gtserror.WithCode) {
targetStatus, err := p.db.GetStatusByID(targetStatusID)
func (p *processor) Unfave(ctx context.Context, requestingAccount *gtsmodel.Account, targetStatusID string) (*apimodel.Status, gtserror.WithCode) {
targetStatus, err := p.db.GetStatusByID(ctx, targetStatusID)
if err != nil {
return nil, gtserror.NewErrorNotFound(fmt.Errorf("error fetching status %s: %s", targetStatusID, err))
}
@ -19,7 +38,7 @@ func (p *processor) Unfave(requestingAccount *gtsmodel.Account, targetStatusID s
return nil, gtserror.NewErrorNotFound(fmt.Errorf("no status owner for status %s", targetStatusID))
}
visible, err := p.filter.StatusVisible(targetStatus, requestingAccount)
visible, err := p.filter.StatusVisible(ctx, targetStatus, requestingAccount)
if err != nil {
return nil, gtserror.NewErrorNotFound(fmt.Errorf("error seeing if status %s is visible: %s", targetStatus.ID, err))
}
@ -31,7 +50,7 @@ func (p *processor) Unfave(requestingAccount *gtsmodel.Account, targetStatusID s
var toUnfave bool
gtsFave := &gtsmodel.StatusFave{}
err = p.db.GetWhere([]db.Where{{Key: "status_id", Value: targetStatus.ID}, {Key: "account_id", Value: requestingAccount.ID}}, gtsFave)
err = p.db.GetWhere(ctx, []db.Where{{Key: "status_id", Value: targetStatus.ID}, {Key: "account_id", Value: requestingAccount.ID}}, gtsFave)
if err == nil {
// we have a fave
toUnfave = true
@ -47,7 +66,7 @@ func (p *processor) Unfave(requestingAccount *gtsmodel.Account, targetStatusID s
if toUnfave {
// we had a fave, so take some action to get rid of it
if err := p.db.DeleteWhere([]db.Where{{Key: "status_id", Value: targetStatus.ID}, {Key: "account_id", Value: requestingAccount.ID}}, gtsFave); err != nil {
if err := p.db.DeleteWhere(ctx, []db.Where{{Key: "status_id", Value: targetStatus.ID}, {Key: "account_id", Value: requestingAccount.ID}}, gtsFave); err != nil {
return nil, gtserror.NewErrorInternalError(fmt.Errorf("error unfaveing status: %s", err))
}
@ -61,7 +80,7 @@ func (p *processor) Unfave(requestingAccount *gtsmodel.Account, targetStatusID s
}
}
mastoStatus, err := p.tc.StatusToMasto(targetStatus, requestingAccount)
mastoStatus, err := p.tc.StatusToMasto(ctx, targetStatus, requestingAccount)
if err != nil {
return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting status %s to frontend representation: %s", targetStatus.ID, err))
}

View file

@ -1,6 +1,25 @@
/*
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 status
import (
"context"
"errors"
"fmt"
@ -12,7 +31,7 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/util"
)
func (p *processor) ProcessVisibility(form *apimodel.AdvancedStatusCreateForm, accountDefaultVis gtsmodel.Visibility, status *gtsmodel.Status) error {
func (p *processor) ProcessVisibility(ctx context.Context, form *apimodel.AdvancedStatusCreateForm, accountDefaultVis gtsmodel.Visibility, status *gtsmodel.Status) error {
// by default all flags are set to true
gtsAdvancedVis := &gtsmodel.VisibilityAdvanced{
Federated: true,
@ -83,7 +102,7 @@ func (p *processor) ProcessVisibility(form *apimodel.AdvancedStatusCreateForm, a
return nil
}
func (p *processor) ProcessReplyToID(form *apimodel.AdvancedStatusCreateForm, thisAccountID string, status *gtsmodel.Status) error {
func (p *processor) ProcessReplyToID(ctx context.Context, form *apimodel.AdvancedStatusCreateForm, thisAccountID string, status *gtsmodel.Status) error {
if form.InReplyToID == "" {
return nil
}
@ -98,7 +117,7 @@ func (p *processor) ProcessReplyToID(form *apimodel.AdvancedStatusCreateForm, th
repliedStatus := &gtsmodel.Status{}
repliedAccount := &gtsmodel.Account{}
// check replied status exists + is replyable
if err := p.db.GetByID(form.InReplyToID, repliedStatus); err != nil {
if err := p.db.GetByID(ctx, form.InReplyToID, repliedStatus); err != nil {
if err == db.ErrNoEntries {
return fmt.Errorf("status with id %s not replyable because it doesn't exist", form.InReplyToID)
}
@ -112,14 +131,14 @@ func (p *processor) ProcessReplyToID(form *apimodel.AdvancedStatusCreateForm, th
}
// check replied account is known to us
if err := p.db.GetByID(repliedStatus.AccountID, repliedAccount); err != nil {
if err := p.db.GetByID(ctx, repliedStatus.AccountID, repliedAccount); err != nil {
if err == db.ErrNoEntries {
return fmt.Errorf("status with id %s not replyable because account id %s is not known", form.InReplyToID, repliedStatus.AccountID)
}
return fmt.Errorf("status with id %s not replyable: %s", form.InReplyToID, err)
}
// check if a block exists
if blocked, err := p.db.IsBlocked(thisAccountID, repliedAccount.ID, true); err != nil {
if blocked, err := p.db.IsBlocked(ctx, thisAccountID, repliedAccount.ID, true); err != nil {
if err != db.ErrNoEntries {
return fmt.Errorf("status with id %s not replyable: %s", form.InReplyToID, err)
}
@ -132,7 +151,7 @@ func (p *processor) ProcessReplyToID(form *apimodel.AdvancedStatusCreateForm, th
return nil
}
func (p *processor) ProcessMediaIDs(form *apimodel.AdvancedStatusCreateForm, thisAccountID string, status *gtsmodel.Status) error {
func (p *processor) ProcessMediaIDs(ctx context.Context, form *apimodel.AdvancedStatusCreateForm, thisAccountID string, status *gtsmodel.Status) error {
if form.MediaIDs == nil {
return nil
}
@ -142,7 +161,7 @@ func (p *processor) ProcessMediaIDs(form *apimodel.AdvancedStatusCreateForm, thi
for _, mediaID := range form.MediaIDs {
// check these attachments exist
a := &gtsmodel.MediaAttachment{}
if err := p.db.GetByID(mediaID, a); err != nil {
if err := p.db.GetByID(ctx, mediaID, a); err != nil {
return fmt.Errorf("invalid media type or media not found for media id %s", mediaID)
}
// check they belong to the requesting account id
@ -161,7 +180,7 @@ func (p *processor) ProcessMediaIDs(form *apimodel.AdvancedStatusCreateForm, thi
return nil
}
func (p *processor) ProcessLanguage(form *apimodel.AdvancedStatusCreateForm, accountDefaultLanguage string, status *gtsmodel.Status) error {
func (p *processor) ProcessLanguage(ctx context.Context, form *apimodel.AdvancedStatusCreateForm, accountDefaultLanguage string, status *gtsmodel.Status) error {
if form.Language != "" {
status.Language = form.Language
} else {
@ -173,9 +192,9 @@ func (p *processor) ProcessLanguage(form *apimodel.AdvancedStatusCreateForm, acc
return nil
}
func (p *processor) ProcessMentions(form *apimodel.AdvancedStatusCreateForm, accountID string, status *gtsmodel.Status) error {
func (p *processor) ProcessMentions(ctx context.Context, form *apimodel.AdvancedStatusCreateForm, accountID string, status *gtsmodel.Status) error {
menchies := []string{}
gtsMenchies, err := p.db.MentionStringsToMentions(util.DeriveMentionsFromStatus(form.Status), accountID, status.ID)
gtsMenchies, err := p.db.MentionStringsToMentions(ctx, util.DeriveMentionsFromStatus(form.Status), accountID, status.ID)
if err != nil {
return fmt.Errorf("error generating mentions from status: %s", err)
}
@ -186,7 +205,7 @@ func (p *processor) ProcessMentions(form *apimodel.AdvancedStatusCreateForm, acc
}
menchie.ID = menchieID
if err := p.db.Put(menchie); err != nil {
if err := p.db.Put(ctx, menchie); err != nil {
return fmt.Errorf("error putting mentions in db: %s", err)
}
menchies = append(menchies, menchie.ID)
@ -198,14 +217,14 @@ func (p *processor) ProcessMentions(form *apimodel.AdvancedStatusCreateForm, acc
return nil
}
func (p *processor) ProcessTags(form *apimodel.AdvancedStatusCreateForm, accountID string, status *gtsmodel.Status) error {
func (p *processor) ProcessTags(ctx context.Context, form *apimodel.AdvancedStatusCreateForm, accountID string, status *gtsmodel.Status) error {
tags := []string{}
gtsTags, err := p.db.TagStringsToTags(util.DeriveHashtagsFromStatus(form.Status), accountID, status.ID)
gtsTags, err := p.db.TagStringsToTags(ctx, util.DeriveHashtagsFromStatus(form.Status), accountID, status.ID)
if err != nil {
return fmt.Errorf("error generating hashtags from status: %s", err)
}
for _, tag := range gtsTags {
if err := p.db.Upsert(tag, "name"); err != nil {
if err := p.db.Upsert(ctx, tag, "name"); err != nil {
return fmt.Errorf("error putting tags in db: %s", err)
}
tags = append(tags, tag.ID)
@ -217,9 +236,9 @@ func (p *processor) ProcessTags(form *apimodel.AdvancedStatusCreateForm, account
return nil
}
func (p *processor) ProcessEmojis(form *apimodel.AdvancedStatusCreateForm, accountID string, status *gtsmodel.Status) error {
func (p *processor) ProcessEmojis(ctx context.Context, form *apimodel.AdvancedStatusCreateForm, accountID string, status *gtsmodel.Status) error {
emojis := []string{}
gtsEmojis, err := p.db.EmojiStringsToEmojis(util.DeriveEmojisFromStatus(form.Status), accountID, status.ID)
gtsEmojis, err := p.db.EmojiStringsToEmojis(ctx, util.DeriveEmojisFromStatus(form.Status), accountID, status.ID)
if err != nil {
return fmt.Errorf("error generating emojis from status: %s", err)
}
@ -233,7 +252,7 @@ func (p *processor) ProcessEmojis(form *apimodel.AdvancedStatusCreateForm, accou
return nil
}
func (p *processor) ProcessContent(form *apimodel.AdvancedStatusCreateForm, accountID string, status *gtsmodel.Status) error {
func (p *processor) ProcessContent(ctx context.Context, form *apimodel.AdvancedStatusCreateForm, accountID string, status *gtsmodel.Status) error {
// if there's nothing in the status at all we can just return early
if form.Status == "" {
status.Content = ""
@ -252,9 +271,9 @@ func (p *processor) ProcessContent(form *apimodel.AdvancedStatusCreateForm, acco
var formatted string
switch form.Format {
case apimodel.StatusFormatPlain:
formatted = p.formatter.FromPlain(content, status.Mentions, status.Tags)
formatted = p.formatter.FromPlain(ctx, content, status.Mentions, status.Tags)
case apimodel.StatusFormatMarkdown:
formatted = p.formatter.FromMarkdown(content, status.Mentions, status.Tags)
formatted = p.formatter.FromMarkdown(ctx, content, status.Mentions, status.Tags)
default:
return fmt.Errorf("format %s not recognised as a valid status format", form.Format)
}

View file

@ -1,6 +1,25 @@
/*
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 status_test
import (
"context"
"fmt"
"testing"
@ -88,7 +107,7 @@ func (suite *UtilTestSuite) TestProcessMentions1() {
ID: "01FCTDD78JJMX3K9KPXQ7ZQ8BJ",
}
err := suite.status.ProcessMentions(form, creatingAccount.ID, status)
err := suite.status.ProcessMentions(context.Background(), form, creatingAccount.ID, status)
assert.NoError(suite.T(), err)
assert.Len(suite.T(), status.Mentions, 1)
@ -138,11 +157,11 @@ func (suite *UtilTestSuite) TestProcessContentFull1() {
ID: "01FCTDD78JJMX3K9KPXQ7ZQ8BJ",
}
err := suite.status.ProcessMentions(form, creatingAccount.ID, status)
err := suite.status.ProcessMentions(context.Background(), form, creatingAccount.ID, status)
assert.NoError(suite.T(), err)
assert.Empty(suite.T(), status.Content) // shouldn't be set yet
err = suite.status.ProcessTags(form, creatingAccount.ID, status)
err = suite.status.ProcessTags(context.Background(), form, creatingAccount.ID, status)
assert.NoError(suite.T(), err)
assert.Empty(suite.T(), status.Content) // shouldn't be set yet
@ -150,7 +169,7 @@ func (suite *UtilTestSuite) TestProcessContentFull1() {
ACTUAL TEST
*/
err = suite.status.ProcessContent(form, creatingAccount.ID, status)
err = suite.status.ProcessContent(context.Background(), form, creatingAccount.ID, status)
assert.NoError(suite.T(), err)
assert.Equal(suite.T(), statusText1ExpectedFull, status.Content)
}
@ -187,7 +206,7 @@ func (suite *UtilTestSuite) TestProcessContentPartial1() {
ID: "01FCTDD78JJMX3K9KPXQ7ZQ8BJ",
}
err := suite.status.ProcessMentions(form, creatingAccount.ID, status)
err := suite.status.ProcessMentions(context.Background(), form, creatingAccount.ID, status)
assert.NoError(suite.T(), err)
assert.Empty(suite.T(), status.Content) // shouldn't be set yet
@ -195,7 +214,7 @@ func (suite *UtilTestSuite) TestProcessContentPartial1() {
ACTUAL TEST
*/
err = suite.status.ProcessContent(form, creatingAccount.ID, status)
err = suite.status.ProcessContent(context.Background(), form, creatingAccount.ID, status)
assert.NoError(suite.T(), err)
assert.Equal(suite.T(), statusText1ExpectedPartial, status.Content)
}
@ -229,7 +248,7 @@ func (suite *UtilTestSuite) TestProcessMentions2() {
ID: "01FCTDD78JJMX3K9KPXQ7ZQ8BJ",
}
err := suite.status.ProcessMentions(form, creatingAccount.ID, status)
err := suite.status.ProcessMentions(context.Background(), form, creatingAccount.ID, status)
assert.NoError(suite.T(), err)
assert.Len(suite.T(), status.Mentions, 1)
@ -279,11 +298,11 @@ func (suite *UtilTestSuite) TestProcessContentFull2() {
ID: "01FCTDD78JJMX3K9KPXQ7ZQ8BJ",
}
err := suite.status.ProcessMentions(form, creatingAccount.ID, status)
err := suite.status.ProcessMentions(context.Background(), form, creatingAccount.ID, status)
assert.NoError(suite.T(), err)
assert.Empty(suite.T(), status.Content) // shouldn't be set yet
err = suite.status.ProcessTags(form, creatingAccount.ID, status)
err = suite.status.ProcessTags(context.Background(), form, creatingAccount.ID, status)
assert.NoError(suite.T(), err)
assert.Empty(suite.T(), status.Content) // shouldn't be set yet
@ -291,7 +310,7 @@ func (suite *UtilTestSuite) TestProcessContentFull2() {
ACTUAL TEST
*/
err = suite.status.ProcessContent(form, creatingAccount.ID, status)
err = suite.status.ProcessContent(context.Background(), form, creatingAccount.ID, status)
assert.NoError(suite.T(), err)
assert.Equal(suite.T(), status2TextExpectedFull, status.Content)
@ -329,7 +348,7 @@ func (suite *UtilTestSuite) TestProcessContentPartial2() {
ID: "01FCTDD78JJMX3K9KPXQ7ZQ8BJ",
}
err := suite.status.ProcessMentions(form, creatingAccount.ID, status)
err := suite.status.ProcessMentions(context.Background(), form, creatingAccount.ID, status)
assert.NoError(suite.T(), err)
assert.Empty(suite.T(), status.Content) // shouldn't be set yet
@ -337,7 +356,7 @@ func (suite *UtilTestSuite) TestProcessContentPartial2() {
ACTUAL TEST
*/
err = suite.status.ProcessContent(form, creatingAccount.ID, status)
err = suite.status.ProcessContent(context.Background(), form, creatingAccount.ID, status)
assert.NoError(suite.T(), err)
fmt.Println(status.Content)

View file

@ -19,14 +19,16 @@
package processing
import (
"context"
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
)
func (p *processor) AuthorizeStreamingRequest(accessToken string) (*gtsmodel.Account, error) {
return p.streamingProcessor.AuthorizeStreamingRequest(accessToken)
func (p *processor) AuthorizeStreamingRequest(ctx context.Context, accessToken string) (*gtsmodel.Account, error) {
return p.streamingProcessor.AuthorizeStreamingRequest(ctx, accessToken)
}
func (p *processor) OpenStreamForAccount(account *gtsmodel.Account, streamType string) (*gtsmodel.Stream, gtserror.WithCode) {
return p.streamingProcessor.OpenStreamForAccount(account, streamType)
func (p *processor) OpenStreamForAccount(ctx context.Context, account *gtsmodel.Account, streamType string) (*gtsmodel.Stream, gtserror.WithCode) {
return p.streamingProcessor.OpenStreamForAccount(ctx, account, streamType)
}

View file

@ -7,7 +7,7 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
)
func (p *processor) AuthorizeStreamingRequest(accessToken string) (*gtsmodel.Account, error) {
func (p *processor) AuthorizeStreamingRequest(ctx context.Context, accessToken string) (*gtsmodel.Account, error) {
ti, err := p.oauthServer.LoadAccessToken(context.Background(), accessToken)
if err != nil {
return nil, fmt.Errorf("AuthorizeStreamingRequest: error loading access token: %s", err)
@ -20,12 +20,12 @@ func (p *processor) AuthorizeStreamingRequest(accessToken string) (*gtsmodel.Acc
// fetch user's and account for this user id
user := &gtsmodel.User{}
if err := p.db.GetByID(uid, user); err != nil || user == nil {
if err := p.db.GetByID(ctx, uid, user); err != nil || user == nil {
return nil, fmt.Errorf("AuthorizeStreamingRequest: no user found for validated uid %s", uid)
}
acct := &gtsmodel.Account{}
if err := p.db.GetByID(user.AccountID, acct); err != nil || acct == nil {
if err := p.db.GetByID(ctx, user.AccountID, acct); err != nil || acct == nil {
return nil, fmt.Errorf("AuthorizeStreamingRequest: no account retrieved for user with id %s", uid)
}

View file

@ -1,6 +1,7 @@
package streaming
import (
"context"
"errors"
"fmt"
@ -10,7 +11,7 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/id"
)
func (p *processor) OpenStreamForAccount(account *gtsmodel.Account, streamType string) (*gtsmodel.Stream, gtserror.WithCode) {
func (p *processor) OpenStreamForAccount(ctx context.Context, account *gtsmodel.Account, streamType string) (*gtsmodel.Stream, gtserror.WithCode) {
l := p.log.WithFields(logrus.Fields{
"func": "OpenStreamForAccount",
"account": account.ID,

View file

@ -1,6 +1,7 @@
package streaming
import (
"context"
"sync"
"github.com/sirupsen/logrus"
@ -17,9 +18,9 @@ import (
// Processor wraps a bunch of functions for processing streaming.
type Processor interface {
// AuthorizeStreamingRequest returns an oauth2 token info in response to an access token query from the streaming API
AuthorizeStreamingRequest(accessToken string) (*gtsmodel.Account, error)
AuthorizeStreamingRequest(ctx context.Context, accessToken string) (*gtsmodel.Account, error)
// OpenStreamForAccount returns a new Stream for the given account, which will contain a channel for passing messages back to the caller.
OpenStreamForAccount(account *gtsmodel.Account, streamType string) (*gtsmodel.Stream, gtserror.WithCode)
OpenStreamForAccount(ctx context.Context, account *gtsmodel.Account, streamType string) (*gtsmodel.Stream, gtserror.WithCode)
// StreamStatusToAccount streams the given status to any open, appropriate streams belonging to the given account.
StreamStatusToAccount(s *apimodel.Status, account *gtsmodel.Account) error
// StreamNotificationToAccount streams the given notification to any open, appropriate streams belonging to the given account.

View file

@ -19,6 +19,7 @@
package processing
import (
"context"
"fmt"
"net/url"
@ -58,8 +59,8 @@ func (p *processor) packageStatusResponse(statuses []*apimodel.Status, path stri
return resp, nil
}
func (p *processor) HomeTimelineGet(authed *oauth.Auth, maxID string, sinceID string, minID string, limit int, local bool) (*apimodel.StatusTimelineResponse, gtserror.WithCode) {
statuses, err := p.timelineManager.HomeTimeline(authed.Account.ID, maxID, sinceID, minID, limit, local)
func (p *processor) HomeTimelineGet(ctx context.Context, authed *oauth.Auth, maxID string, sinceID string, minID string, limit int, local bool) (*apimodel.StatusTimelineResponse, gtserror.WithCode) {
statuses, err := p.timelineManager.HomeTimeline(ctx, authed.Account.ID, maxID, sinceID, minID, limit, local)
if err != nil {
return nil, gtserror.NewErrorInternalError(err)
}
@ -73,8 +74,8 @@ func (p *processor) HomeTimelineGet(authed *oauth.Auth, maxID string, sinceID st
return p.packageStatusResponse(statuses, "api/v1/timelines/home", statuses[len(statuses)-1].ID, statuses[0].ID, limit)
}
func (p *processor) PublicTimelineGet(authed *oauth.Auth, maxID string, sinceID string, minID string, limit int, local bool) (*apimodel.StatusTimelineResponse, gtserror.WithCode) {
statuses, err := p.db.GetPublicTimeline(authed.Account.ID, maxID, sinceID, minID, limit, local)
func (p *processor) PublicTimelineGet(ctx context.Context, authed *oauth.Auth, maxID string, sinceID string, minID string, limit int, local bool) (*apimodel.StatusTimelineResponse, gtserror.WithCode) {
statuses, err := p.db.GetPublicTimeline(ctx, authed.Account.ID, maxID, sinceID, minID, limit, local)
if err != nil {
if err == db.ErrNoEntries {
// there are just no entries left
@ -86,7 +87,7 @@ func (p *processor) PublicTimelineGet(authed *oauth.Auth, maxID string, sinceID
return nil, gtserror.NewErrorInternalError(err)
}
s, err := p.filterPublicStatuses(authed, statuses)
s, err := p.filterPublicStatuses(ctx, authed, statuses)
if err != nil {
return nil, gtserror.NewErrorInternalError(err)
}
@ -94,8 +95,8 @@ func (p *processor) PublicTimelineGet(authed *oauth.Auth, maxID string, sinceID
return p.packageStatusResponse(s, "api/v1/timelines/public", s[len(s)-1].ID, s[0].ID, limit)
}
func (p *processor) FavedTimelineGet(authed *oauth.Auth, maxID string, minID string, limit int) (*apimodel.StatusTimelineResponse, gtserror.WithCode) {
statuses, nextMaxID, prevMinID, err := p.db.GetFavedTimeline(authed.Account.ID, maxID, minID, limit)
func (p *processor) FavedTimelineGet(ctx context.Context, authed *oauth.Auth, maxID string, minID string, limit int) (*apimodel.StatusTimelineResponse, gtserror.WithCode) {
statuses, nextMaxID, prevMinID, err := p.db.GetFavedTimeline(ctx, authed.Account.ID, maxID, minID, limit)
if err != nil {
if err == db.ErrNoEntries {
// there are just no entries left
@ -107,7 +108,7 @@ func (p *processor) FavedTimelineGet(authed *oauth.Auth, maxID string, minID str
return nil, gtserror.NewErrorInternalError(err)
}
s, err := p.filterFavedStatuses(authed, statuses)
s, err := p.filterFavedStatuses(ctx, authed, statuses)
if err != nil {
return nil, gtserror.NewErrorInternalError(err)
}
@ -115,13 +116,13 @@ func (p *processor) FavedTimelineGet(authed *oauth.Auth, maxID string, minID str
return p.packageStatusResponse(s, "api/v1/favourites", nextMaxID, prevMinID, limit)
}
func (p *processor) filterPublicStatuses(authed *oauth.Auth, statuses []*gtsmodel.Status) ([]*apimodel.Status, error) {
func (p *processor) filterPublicStatuses(ctx context.Context, authed *oauth.Auth, statuses []*gtsmodel.Status) ([]*apimodel.Status, error) {
l := p.log.WithField("func", "filterPublicStatuses")
apiStatuses := []*apimodel.Status{}
for _, s := range statuses {
targetAccount := &gtsmodel.Account{}
if err := p.db.GetByID(s.AccountID, targetAccount); err != nil {
if err := p.db.GetByID(ctx, s.AccountID, targetAccount); err != nil {
if err == db.ErrNoEntries {
l.Debugf("filterPublicStatuses: skipping status %s because account %s can't be found in the db", s.ID, s.AccountID)
continue
@ -129,7 +130,7 @@ func (p *processor) filterPublicStatuses(authed *oauth.Auth, statuses []*gtsmode
return nil, gtserror.NewErrorInternalError(fmt.Errorf("filterPublicStatuses: error getting status author: %s", err))
}
timelineable, err := p.filter.StatusPublictimelineable(s, authed.Account)
timelineable, err := p.filter.StatusPublictimelineable(ctx, s, authed.Account)
if err != nil {
l.Debugf("filterPublicStatuses: skipping status %s because of an error checking status visibility: %s", s.ID, err)
continue
@ -138,7 +139,7 @@ func (p *processor) filterPublicStatuses(authed *oauth.Auth, statuses []*gtsmode
continue
}
apiStatus, err := p.tc.StatusToMasto(s, authed.Account)
apiStatus, err := p.tc.StatusToMasto(ctx, s, authed.Account)
if err != nil {
l.Debugf("filterPublicStatuses: skipping status %s because it couldn't be converted to its mastodon representation: %s", s.ID, err)
continue
@ -150,13 +151,13 @@ func (p *processor) filterPublicStatuses(authed *oauth.Auth, statuses []*gtsmode
return apiStatuses, nil
}
func (p *processor) filterFavedStatuses(authed *oauth.Auth, statuses []*gtsmodel.Status) ([]*apimodel.Status, error) {
func (p *processor) filterFavedStatuses(ctx context.Context, authed *oauth.Auth, statuses []*gtsmodel.Status) ([]*apimodel.Status, error) {
l := p.log.WithField("func", "filterFavedStatuses")
apiStatuses := []*apimodel.Status{}
for _, s := range statuses {
targetAccount := &gtsmodel.Account{}
if err := p.db.GetByID(s.AccountID, targetAccount); err != nil {
if err := p.db.GetByID(ctx, s.AccountID, targetAccount); err != nil {
if err == db.ErrNoEntries {
l.Debugf("filterFavedStatuses: skipping status %s because account %s can't be found in the db", s.ID, s.AccountID)
continue
@ -164,7 +165,7 @@ func (p *processor) filterFavedStatuses(authed *oauth.Auth, statuses []*gtsmodel
return nil, gtserror.NewErrorInternalError(fmt.Errorf("filterPublicStatuses: error getting status author: %s", err))
}
timelineable, err := p.filter.StatusVisible(s, authed.Account)
timelineable, err := p.filter.StatusVisible(ctx, s, authed.Account)
if err != nil {
l.Debugf("filterFavedStatuses: skipping status %s because of an error checking status visibility: %s", s.ID, err)
continue
@ -173,7 +174,7 @@ func (p *processor) filterFavedStatuses(authed *oauth.Auth, statuses []*gtsmodel
continue
}
apiStatus, err := p.tc.StatusToMasto(s, authed.Account)
apiStatus, err := p.tc.StatusToMasto(ctx, s, authed.Account)
if err != nil {
l.Debugf("filterFavedStatuses: skipping status %s because it couldn't be converted to its mastodon representation: %s", s.ID, err)
continue

View file

@ -70,7 +70,7 @@ func NewTestDB() db.DB {
l.SetLevel(logrus.TraceLevel)
testDB, err := pg.NewPostgresService(context.Background(), config, l)
if err != nil {
panic(err)
logrus.Panic(err)
}
return testDB
}
@ -84,115 +84,124 @@ func NewTestDB() db.DB {
// signatures with, otherwise this function will randomly generate new keys for accounts and signature
// verification will fail.
func StandardDBSetup(db db.DB, accounts map[string]*gtsmodel.Account) {
if db == nil {
logrus.Panic("db setup: db was nil")
}
ctx := context.Background()
for _, m := range testModels {
if err := db.CreateTable(ctx, m); err != nil {
panic(err)
logrus.Panic(err)
}
}
for _, v := range NewTestTokens() {
if err := db.Put(ctx, v); err != nil {
panic(err)
logrus.Panic(err)
}
}
for _, v := range NewTestClients() {
if err := db.Put(ctx, v); err != nil {
panic(err)
logrus.Panic(err)
}
}
for _, v := range NewTestApplications() {
if err := db.Put(ctx, v); err != nil {
panic(err)
logrus.Panic(err)
}
}
for _, v := range NewTestUsers() {
if err := db.Put(ctx, v); err != nil {
panic(err)
logrus.Panic(err)
}
}
if accounts == nil {
for _, v := range NewTestAccounts() {
if err := db.Put(ctx, v); err != nil {
panic(err)
logrus.Panic(err)
}
}
} else {
for _, v := range accounts {
if err := db.Put(ctx, v); err != nil {
panic(err)
logrus.Panic(err)
}
}
}
for _, v := range NewTestAttachments() {
if err := db.Put(ctx, v); err != nil {
panic(err)
logrus.Panic(err)
}
}
for _, v := range NewTestStatuses() {
if err := db.PutStatus(ctx, v); err != nil {
panic(err)
logrus.Panic(err)
}
}
for _, v := range NewTestEmojis() {
if err := db.Put(ctx, v); err != nil {
panic(err)
logrus.Panic(err)
}
}
for _, v := range NewTestTags() {
if err := db.Put(ctx, v); err != nil {
panic(err)
logrus.Panic(err)
}
}
for _, v := range NewTestMentions() {
if err := db.Put(ctx, v); err != nil {
panic(err)
logrus.Panic(err)
}
}
for _, v := range NewTestFaves() {
if err := db.Put(ctx, v); err != nil {
panic(err)
logrus.Panic(err)
}
}
for _, v := range NewTestFollows() {
if err := db.Put(ctx, v); err != nil {
panic(err)
logrus.Panic(err)
}
}
for _, v := range NewTestNotifications() {
if err := db.Put(ctx, v); err != nil {
panic(err)
logrus.Panic(err)
}
}
if err := db.CreateInstanceAccount(ctx); err != nil {
panic(err)
logrus.Panic(err)
}
if err := db.CreateInstanceInstance(ctx); err != nil {
panic(err)
logrus.Panic(err)
}
logrus.Debug("testing db setup complete")
}
// StandardDBTeardown drops all the standard testing tables/models from the database to ensure it's clean for the next test.
func StandardDBTeardown(db db.DB) {
ctx := context.Background()
if db == nil {
logrus.Panic("db teardown: db was nil")
}
for _, m := range testModels {
if err := db.DropTable(ctx, m); err != nil {
panic(err)
logrus.Panic(err)
}
}
}

View file

@ -19,13 +19,15 @@
package testrig
import (
"context"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/router"
)
// NewTestRouter returns a Router suitable for testing
func NewTestRouter(db db.DB) router.Router {
r, err := router.New(NewTestConfig(), db, NewTestLog())
r, err := router.New(context.Background(), NewTestConfig(), db, NewTestLog())
if err != nil {
panic(err)
}

View file

@ -1,18 +0,0 @@
run:
concurrency: 8
deadline: 5m
tests: false
linters:
enable-all: true
disable:
- gochecknoglobals
- gocognit
- gomnd
- wsl
- funlen
- godox
- goerr113
- exhaustive
- nestif
- gofumpt
- goconst

View file

@ -1,4 +0,0 @@
semi: false
singleQuote: true
proseWrap: always
printWidth: 100

View file

@ -1,21 +0,0 @@
dist: xenial
language: go
addons:
postgresql: '9.6'
go:
- 1.14.x
- 1.15.x
- tip
matrix:
allow_failures:
- go: tip
go_import_path: github.com/go-pg/pg
before_install:
- psql -U postgres -c "CREATE EXTENSION hstore"
- curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s --
-b $(go env GOPATH)/bin v1.28.3

View file

@ -1,204 +0,0 @@
# Changelog
> :heart:
> [**Uptrace.dev** - All-in-one tool to optimize performance and monitor errors & logs](https://uptrace.dev)
**Important**. Please check [Bun](https://bun.uptrace.dev/guide/pg-migration.html) - the next
iteration of go-pg built on top of `sql.DB`.
## v10.10
- Removed extra OpenTelemetry spans from go-pg core. Now go-pg instrumentation only adds a single
span with a SQL query (instead of 4 spans). There are multiple reasons behind this decision:
- Traces become smaller and less noisy.
- [Bun](https://github.com/uptrace/bun) can't support the same level of instrumentation and it is
nice to keep the projects synced.
- It may be costly to process those 3 extra spans for each query.
Eventually we hope to replace the information that we no longer collect with OpenTelemetry
Metrics.
## v10.9
- To make updating easier, extra modules now have the same version as go-pg does. That means that
you need to update your imports:
```
github.com/go-pg/pg/extra/pgdebug -> github.com/go-pg/pg/extra/pgdebug/v10
github.com/go-pg/pg/extra/pgotel -> github.com/go-pg/pg/extra/pgotel/v10
github.com/go-pg/pg/extra/pgsegment -> github.com/go-pg/pg/extra/pgsegment/v10
```
- Exported `pg.Query` which should be used instead of `orm.Query`.
- Added `pg.DBI` which is a DB interface implemented by `pg.DB` and `pg.Tx`.
## v10
### Resources
- Docs at https://pg.uptrace.dev/ powered by [mkdocs](https://github.com/squidfunk/mkdocs-material).
- [RealWorld example application](https://github.com/uptrace/go-realworld-example-app).
- [Discord](https://discord.gg/rWtp5Aj).
### Features
- `Select`, `Insert`, and `Update` support `map[string]interface{}`. `Select` also supports
`[]map[string]interface{}`.
```go
var mm []map[string]interface{}
err := db.Model((*User)(nil)).Limit(10).Select(&mm)
```
- Columns that start with `_` are ignored if there is no destination field.
- Optional [faster json encoding](https://github.com/go-pg/pgext).
- Added [pgext.OpenTelemetryHook](https://github.com/go-pg/pgext) that adds
[OpenTelemetry instrumentation](https://pg.uptrace.dev/tracing/).
- Added [pgext.DebugHook](https://github.com/go-pg/pgext) that logs failed queries.
- Added `db.Ping` to check if database is healthy.
### Changes
- ORM relations are reworked and now require `rel` tag option (but existing code will continue
working until v11). Supported options:
- `pg:"rel:has-one"` - has one relation.
- `pg:"rel:belongs-to"` - belongs to relation.
- `pg:"rel:has-many"` - has many relation.
- `pg:"many2many:book_genres"` - many to many relation.
- Changed `pg.QueryHook` to return temp byte slice to reduce memory usage.
- `,msgpack` struct tag marshals data in MessagePack format using
https://github.com/vmihailenco/msgpack
- Empty slices and maps are no longer marshaled as `NULL`. Nil slices and maps are still marshaled
as `NULL`.
- Changed `UpdateNotZero` to include zero fields with `pg:",use_zero"` tag. Consider using
`Model(*map[string]interface{})` for inserts and updates.
- `joinFK` is deprecated in favor of `join_fk`.
- `partitionBy` is deprecated in favor of `partition_by`.
- ORM shortcuts are removed:
- `db.Select(model)` becomes `db.Model(model).WherePK().Select()`.
- `db.Insert(model)` becomes `db.Model(model).Insert()`.
- `db.Update(model)` becomes `db.Model(model).WherePK().Update()`.
- `db.Delete(model)` becomes `db.Model(model).WherePK().Delete()`.
- Deprecated types and funcs are removed.
- `WhereStruct` is removed.
## v9
- `pg:",notnull"` is reworked. Now it means SQL `NOT NULL` constraint and nothing more.
- Added `pg:",use_zero"` to prevent go-pg from converting Go zero values to SQL `NULL`.
- UpdateNotNull is renamed to UpdateNotZero. As previously it omits zero Go values, but it does not
take in account if field is nullable or not.
- ORM supports DistinctOn.
- Hooks accept and return context.
- Client respects Context.Deadline when setting net.Conn deadline.
- Client listens on Context.Done while waiting for a connection from the pool and returns an error
when context is cancelled.
- `Query.Column` does not accept relation name any more. Use `Query.Relation` instead which returns
an error if relation does not exist.
- urlvalues package is removed in favor of https://github.com/go-pg/urlstruct. You can also use
struct based filters via `Query.WhereStruct`.
- `NewModel` and `AddModel` methods of `HooklessModel` interface were renamed to `NextColumnScanner`
and `AddColumnScanner` respectively.
- `types.F` and `pg.F` are deprecated in favor of `pg.Ident`.
- `types.Q` is deprecated in favor of `pg.Safe`.
- `pg.Q` is deprecated in favor of `pg.SafeQuery`.
- `TableName` field is deprecated in favor of `tableName`.
- Always use `pg:"..."` struct field tag instead of `sql:"..."`.
- `pg:",override"` is deprecated in favor of `pg:",inherit"`.
## v8
- Added `QueryContext`, `ExecContext`, and `ModelContext` which accept `context.Context`. Queries
are cancelled when context is cancelled.
- Model hooks are changed to accept `context.Context` as first argument.
- Fixed array and hstore parsers to handle multiple single quotes (#1235).
## v7
- DB.OnQueryProcessed is replaced with DB.AddQueryHook.
- Added WhereStruct.
- orm.Pager is moved to urlvalues.Pager. Pager.FromURLValues returns an error if page or limit
params can't be parsed.
## v6.16
- Read buffer is re-worked. Default read buffer is increased to 65kb.
## v6.15
- Added Options.MinIdleConns.
- Options.MaxAge renamed to Options.MaxConnAge.
- PoolStats.FreeConns is renamed to PoolStats.IdleConns.
- New hook BeforeSelectQuery.
- `,override` is renamed to `,inherit`.
- Dialer.KeepAlive is set to 5 minutes by default.
- Added support "scram-sha-256" authentication.
## v6.14
- Fields ignored with `sql:"-"` tag are no longer considered by ORM relation detector.
## v6.12
- `Insert`, `Update`, and `Delete` can return `pg.ErrNoRows` and `pg.ErrMultiRows` when `Returning`
is used and model expects single row.
## v6.11
- `db.Model(&strct).Update()` and `db.Model(&strct).Delete()` no longer adds WHERE condition based
on primary key when there are no conditions. Instead you should use `db.Update(&strct)` or
`db.Model(&strct).WherePK().Update()`.
## v6.10
- `?Columns` is renamed to `?TableColumns`. `?Columns` is changed to produce column names without
table alias.
## v6.9
- `pg:"fk"` tag now accepts SQL names instead of Go names, e.g. `pg:"fk:ParentId"` becomes
`pg:"fk:parent_id"`. Old code should continue working in most cases, but it is strongly advised to
start using new convention.
- uint and uint64 SQL type is changed from decimal to bigint according to the lesser of two evils
principle. Use `sql:"type:decimal"` to get old behavior.
## v6.8
- `CreateTable` no longer adds ON DELETE hook by default. To get old behavior users should add
`sql:"on_delete:CASCADE"` tag on foreign key field.
## v6
- `types.Result` is renamed to `orm.Result`.
- Added `OnQueryProcessed` event that can be used to log / report queries timing. Query logger is
removed.
- `orm.URLValues` is renamed to `orm.URLFilters`. It no longer adds ORDER clause.
- `orm.Pager` is renamed to `orm.Pagination`.
- Support for net.IP and net.IPNet.
- Support for context.Context.
- Bulk/multi updates.
- Query.WhereGroup for enclosing conditions in parentheses.
## v5
- All fields are nullable by default. `,null` tag is replaced with `,notnull`.
- `Result.Affected` renamed to `Result.RowsAffected`.
- Added `Result.RowsReturned`.
- `Create` renamed to `Insert`, `BeforeCreate` to `BeforeInsert`, `AfterCreate` to `AfterInsert`.
- Indexed placeholders support, e.g. `db.Exec("SELECT ?0 + ?0", 1)`.
- Named placeholders are evaluated when query is executed.
- Added Update and Delete hooks.
- Order reworked to quote column names. OrderExpr added to bypass Order quoting restrictions.
- Group reworked to quote column names. GroupExpr added to bypass Group quoting restrictions.
## v4
- `Options.Host` and `Options.Port` merged into `Options.Addr`.
- Added `Options.MaxRetries`. Now queries are not retried by default.
- `LoadInto` renamed to `Scan`, `ColumnLoader` renamed to `ColumnScanner`, LoadColumn renamed to
ScanColumn, `NewRecord() interface{}` changed to `NewModel() ColumnScanner`,
`AppendQuery(dst []byte) []byte` changed to `AppendValue(dst []byte, quote bool) ([]byte, error)`.
- Structs, maps and slices are marshalled to JSON by default.
- Added support for scanning slices, .e.g. scanning `[]int`.
- Added object relational mapping.

View file

@ -1,24 +0,0 @@
Copyright (c) 2013 github.com/go-pg/pg Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View file

@ -1,27 +0,0 @@
all:
TZ= go test ./...
TZ= go test ./... -short -race
TZ= go test ./... -run=NONE -bench=. -benchmem
env GOOS=linux GOARCH=386 go test ./...
go vet
golangci-lint run
.PHONY: cleanTest
cleanTest:
docker rm -fv pg || true
.PHONY: pre-test
pre-test: cleanTest
docker run -d --name pg -p 5432:5432 -e POSTGRES_HOST_AUTH_METHOD=trust postgres:9.6
sleep 10
docker exec pg psql -U postgres -c "CREATE EXTENSION hstore"
.PHONY: test
test: pre-test
TZ= PGSSLMODE=disable go test ./... -v
tag:
git tag $(VERSION)
git tag extra/pgdebug/$(VERSION)
git tag extra/pgotel/$(VERSION)
git tag extra/pgsegment/$(VERSION)

View file

@ -1,240 +0,0 @@
<p align="center">
<a href="https://uptrace.dev/?utm_source=gh-pg&utm_campaign=gh-pg-banner1">
<img src="https://raw.githubusercontent.com/uptrace/roadmap/master/banner1.png">
</a>
</p>
# PostgreSQL client and ORM for Golang
[![Build Status](https://travis-ci.org/go-pg/pg.svg?branch=v10)](https://travis-ci.org/go-pg/pg)
[![PkgGoDev](https://pkg.go.dev/badge/github.com/go-pg/pg/v10)](https://pkg.go.dev/github.com/go-pg/pg/v10)
[![Documentation](https://img.shields.io/badge/pg-documentation-informational)](https://pg.uptrace.dev/)
[![Chat](https://discordapp.com/api/guilds/752070105847955518/widget.png)](https://discord.gg/rWtp5Aj)
**Important**. Please check [Bun](https://bun.uptrace.dev/guide/pg-migration.html) - the next
iteration of go-pg built on top of `sql.DB`.
- Join [Discord](https://discord.gg/rWtp5Aj) to ask questions.
- [Documentation](https://pg.uptrace.dev)
- [Reference](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc)
- [Examples](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#pkg-examples)
- Example projects:
- [treemux](https://github.com/uptrace/go-treemux-realworld-example-app)
- [gin](https://github.com/gogjango/gjango)
- [go-kit](https://github.com/Tsovak/rest-api-demo)
- [aah framework](https://github.com/kieusonlam/golamapi)
- [GraphQL Tutorial on YouTube](https://www.youtube.com/playlist?list=PLzQWIQOqeUSNwXcneWYJHUREAIucJ5UZn).
## Ecosystem
- Migrations by [vmihailenco](https://github.com/go-pg/migrations) and
[robinjoseph08](https://github.com/robinjoseph08/go-pg-migrations).
- [Genna - cli tool for generating go-pg models](https://github.com/dizzyfool/genna).
- [bigint](https://github.com/d-fal/bigint) - big.Int type for go-pg.
- [urlstruct](https://github.com/go-pg/urlstruct) to decode `url.Values` into structs.
- [Sharding](https://github.com/go-pg/sharding).
- [go-pg-monitor](https://github.com/hypnoglow/go-pg-monitor) - Prometheus metrics based on go-pg
client stats.
## Features
- Basic types: integers, floats, string, bool, time.Time, net.IP, net.IPNet.
- sql.NullBool, sql.NullString, sql.NullInt64, sql.NullFloat64 and
[pg.NullTime](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#NullTime).
- [sql.Scanner](http://golang.org/pkg/database/sql/#Scanner) and
[sql/driver.Valuer](http://golang.org/pkg/database/sql/driver/#Valuer) interfaces.
- Structs, maps and arrays are marshalled as JSON by default.
- PostgreSQL multidimensional Arrays using
[array tag](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB-Model-PostgresArrayStructTag)
and [Array wrapper](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-Array).
- Hstore using
[hstore tag](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB-Model-HstoreStructTag)
and [Hstore wrapper](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-Hstore).
- [Composite types](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB-Model-CompositeType).
- All struct fields are nullable by default and zero values (empty string, 0, zero time, empty map
or slice, nil ptr) are marshalled as SQL `NULL`. `pg:",notnull"` is used to add SQL `NOT NULL`
constraint and `pg:",use_zero"` to allow Go zero values.
- [Transactions](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB-Begin).
- [Prepared statements](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB-Prepare).
- [Notifications](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-Listener) using
`LISTEN` and `NOTIFY`.
- [Copying data](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB-CopyFrom) using
`COPY FROM` and `COPY TO`.
- [Timeouts](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#Options) and canceling queries using
context.Context.
- Automatic connection pooling with
[circuit breaker](https://en.wikipedia.org/wiki/Circuit_breaker_design_pattern) support.
- Queries retry on network errors.
- Working with models using
[ORM](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB.Model) and
[SQL](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB.Query).
- Scanning variables using
[ORM](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB.Model-SelectSomeColumnsIntoVars)
and [SQL](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-Scan).
- [SelectOrInsert](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB.Model-InsertSelectOrInsert)
using on-conflict.
- [INSERT ... ON CONFLICT DO UPDATE](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB.Model-InsertOnConflictDoUpdate)
using ORM.
- Bulk/batch
[inserts](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB.Model-BulkInsert),
[updates](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB.Model-BulkUpdate), and
[deletes](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB.Model-BulkDelete).
- Common table expressions using
[WITH](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB.Model-SelectWith) and
[WrapWith](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB.Model-SelectWrapWith).
- [CountEstimate](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB.Model-CountEstimate)
using `EXPLAIN` to get
[estimated number of matching rows](https://wiki.postgresql.org/wiki/Count_estimate).
- ORM supports
[has one](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB.Model-HasOne),
[belongs to](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB.Model-BelongsTo),
[has many](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB.Model-HasMany), and
[many to many](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB.Model-ManyToMany)
with composite/multi-column primary keys.
- [Soft deletes](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB.Model-SoftDelete).
- [Creating tables from structs](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB.Model-CreateTable).
- [ForEach](https://pkg.go.dev/github.com/go-pg/pg/v10?tab=doc#example-DB.Model-ForEach) that calls
a function for each row returned by the query without loading all rows into the memory.
## Installation
go-pg supports 2 last Go versions and requires a Go version with
[modules](https://github.com/golang/go/wiki/Modules) support. So make sure to initialize a Go
module:
```shell
go mod init github.com/my/repo
```
And then install go-pg (note _v10_ in the import; omitting it is a popular mistake):
```shell
go get github.com/go-pg/pg/v10
```
## Quickstart
```go
package pg_test
import (
"fmt"
"github.com/go-pg/pg/v10"
"github.com/go-pg/pg/v10/orm"
)
type User struct {
Id int64
Name string
Emails []string
}
func (u User) String() string {
return fmt.Sprintf("User<%d %s %v>", u.Id, u.Name, u.Emails)
}
type Story struct {
Id int64
Title string
AuthorId int64
Author *User `pg:"rel:has-one"`
}
func (s Story) String() string {
return fmt.Sprintf("Story<%d %s %s>", s.Id, s.Title, s.Author)
}
func ExampleDB_Model() {
db := pg.Connect(&pg.Options{
User: "postgres",
})
defer db.Close()
err := createSchema(db)
if err != nil {
panic(err)
}
user1 := &User{
Name: "admin",
Emails: []string{"admin1@admin", "admin2@admin"},
}
_, err = db.Model(user1).Insert()
if err != nil {
panic(err)
}
_, err = db.Model(&User{
Name: "root",
Emails: []string{"root1@root", "root2@root"},
}).Insert()
if err != nil {
panic(err)
}
story1 := &Story{
Title: "Cool story",
AuthorId: user1.Id,
}
_, err = db.Model(story1).Insert()
if err != nil {
panic(err)
}
// Select user by primary key.
user := &User{Id: user1.Id}
err = db.Model(user).WherePK().Select()
if err != nil {
panic(err)
}
// Select all users.
var users []User
err = db.Model(&users).Select()
if err != nil {
panic(err)
}
// Select story and associated author in one query.
story := new(Story)
err = db.Model(story).
Relation("Author").
Where("story.id = ?", story1.Id).
Select()
if err != nil {
panic(err)
}
fmt.Println(user)
fmt.Println(users)
fmt.Println(story)
// Output: User<1 admin [admin1@admin admin2@admin]>
// [User<1 admin [admin1@admin admin2@admin]> User<2 root [root1@root root2@root]>]
// Story<1 Cool story User<1 admin [admin1@admin admin2@admin]>>
}
// createSchema creates database schema for User and Story models.
func createSchema(db *pg.DB) error {
models := []interface{}{
(*User)(nil),
(*Story)(nil),
}
for _, model := range models {
err := db.Model(model).CreateTable(&orm.CreateTableOptions{
Temp: true,
})
if err != nil {
return err
}
}
return nil
}
```
## See also
- [Fast and flexible HTTP router](https://github.com/vmihailenco/treemux)
- [Golang msgpack](https://github.com/vmihailenco/msgpack)
- [Golang message task queue](https://github.com/vmihailenco/taskq)

View file

@ -1,618 +0,0 @@
package pg
import (
"context"
"io"
"time"
"github.com/go-pg/pg/v10/internal"
"github.com/go-pg/pg/v10/internal/pool"
"github.com/go-pg/pg/v10/orm"
"github.com/go-pg/pg/v10/types"
)
type baseDB struct {
db orm.DB
opt *Options
pool pool.Pooler
fmter *orm.Formatter
queryHooks []QueryHook
}
// PoolStats contains the stats of a connection pool.
type PoolStats pool.Stats
// PoolStats returns connection pool stats.
func (db *baseDB) PoolStats() *PoolStats {
stats := db.pool.Stats()
return (*PoolStats)(stats)
}
func (db *baseDB) clone() *baseDB {
return &baseDB{
db: db.db,
opt: db.opt,
pool: db.pool,
fmter: db.fmter,
queryHooks: copyQueryHooks(db.queryHooks),
}
}
func (db *baseDB) withPool(p pool.Pooler) *baseDB {
cp := db.clone()
cp.pool = p
return cp
}
func (db *baseDB) WithTimeout(d time.Duration) *baseDB {
newopt := *db.opt
newopt.ReadTimeout = d
newopt.WriteTimeout = d
cp := db.clone()
cp.opt = &newopt
return cp
}
func (db *baseDB) WithParam(param string, value interface{}) *baseDB {
cp := db.clone()
cp.fmter = db.fmter.WithParam(param, value)
return cp
}
// Param returns value for the param.
func (db *baseDB) Param(param string) interface{} {
return db.fmter.Param(param)
}
func (db *baseDB) retryBackoff(retry int) time.Duration {
return internal.RetryBackoff(retry, db.opt.MinRetryBackoff, db.opt.MaxRetryBackoff)
}
func (db *baseDB) getConn(ctx context.Context) (*pool.Conn, error) {
cn, err := db.pool.Get(ctx)
if err != nil {
return nil, err
}
if cn.Inited {
return cn, nil
}
if err := db.initConn(ctx, cn); err != nil {
db.pool.Remove(ctx, cn, err)
// It is safe to reset StickyConnPool if conn can't be initialized.
if p, ok := db.pool.(*pool.StickyConnPool); ok {
_ = p.Reset(ctx)
}
if err := internal.Unwrap(err); err != nil {
return nil, err
}
return nil, err
}
return cn, nil
}
func (db *baseDB) initConn(ctx context.Context, cn *pool.Conn) error {
if cn.Inited {
return nil
}
cn.Inited = true
if db.opt.TLSConfig != nil {
err := db.enableSSL(ctx, cn, db.opt.TLSConfig)
if err != nil {
return err
}
}
err := db.startup(ctx, cn, db.opt.User, db.opt.Password, db.opt.Database, db.opt.ApplicationName)
if err != nil {
return err
}
if db.opt.OnConnect != nil {
p := pool.NewSingleConnPool(db.pool, cn)
return db.opt.OnConnect(ctx, newConn(ctx, db.withPool(p)))
}
return nil
}
func (db *baseDB) releaseConn(ctx context.Context, cn *pool.Conn, err error) {
if isBadConn(err, false) {
db.pool.Remove(ctx, cn, err)
} else {
db.pool.Put(ctx, cn)
}
}
func (db *baseDB) withConn(
ctx context.Context, fn func(context.Context, *pool.Conn) error,
) error {
cn, err := db.getConn(ctx)
if err != nil {
return err
}
var fnDone chan struct{}
if ctx != nil && ctx.Done() != nil {
fnDone = make(chan struct{})
go func() {
select {
case <-fnDone: // fn has finished, skip cancel
case <-ctx.Done():
err := db.cancelRequest(cn.ProcessID, cn.SecretKey)
if err != nil {
internal.Logger.Printf(ctx, "cancelRequest failed: %s", err)
}
// Signal end of conn use.
fnDone <- struct{}{}
}
}()
}
defer func() {
if fnDone == nil {
db.releaseConn(ctx, cn, err)
return
}
select {
case <-fnDone: // wait for cancel to finish request
// Looks like the canceled connection must be always removed from the pool.
db.pool.Remove(ctx, cn, err)
case fnDone <- struct{}{}: // signal fn finish, skip cancel goroutine
db.releaseConn(ctx, cn, err)
}
}()
err = fn(ctx, cn)
return err
}
func (db *baseDB) shouldRetry(err error) bool {
switch err {
case io.EOF, io.ErrUnexpectedEOF:
return true
case nil, context.Canceled, context.DeadlineExceeded:
return false
}
if pgerr, ok := err.(Error); ok {
switch pgerr.Field('C') {
case "40001", // serialization_failure
"53300", // too_many_connections
"55000": // attempted to delete invisible tuple
return true
case "57014": // statement_timeout
return db.opt.RetryStatementTimeout
default:
return false
}
}
if _, ok := err.(timeoutError); ok {
return true
}
return false
}
// Close closes the database client, releasing any open resources.
//
// It is rare to Close a DB, as the DB handle is meant to be
// long-lived and shared between many goroutines.
func (db *baseDB) Close() error {
return db.pool.Close()
}
// Exec executes a query ignoring returned rows. The params are for any
// placeholders in the query.
func (db *baseDB) Exec(query interface{}, params ...interface{}) (res Result, err error) {
return db.exec(db.db.Context(), query, params...)
}
func (db *baseDB) ExecContext(c context.Context, query interface{}, params ...interface{}) (Result, error) {
return db.exec(c, query, params...)
}
func (db *baseDB) exec(ctx context.Context, query interface{}, params ...interface{}) (Result, error) {
wb := pool.GetWriteBuffer()
defer pool.PutWriteBuffer(wb)
if err := writeQueryMsg(wb, db.fmter, query, params...); err != nil {
return nil, err
}
ctx, evt, err := db.beforeQuery(ctx, db.db, nil, query, params, wb.Query())
if err != nil {
return nil, err
}
var res Result
var lastErr error
for attempt := 0; attempt <= db.opt.MaxRetries; attempt++ {
if attempt > 0 {
if err := internal.Sleep(ctx, db.retryBackoff(attempt-1)); err != nil {
return nil, err
}
}
lastErr = db.withConn(ctx, func(ctx context.Context, cn *pool.Conn) error {
res, err = db.simpleQuery(ctx, cn, wb)
return err
})
if !db.shouldRetry(lastErr) {
break
}
}
if err := db.afterQuery(ctx, evt, res, lastErr); err != nil {
return nil, err
}
return res, lastErr
}
// ExecOne acts like Exec, but query must affect only one row. It
// returns ErrNoRows error when query returns zero rows or
// ErrMultiRows when query returns multiple rows.
func (db *baseDB) ExecOne(query interface{}, params ...interface{}) (Result, error) {
return db.execOne(db.db.Context(), query, params...)
}
func (db *baseDB) ExecOneContext(ctx context.Context, query interface{}, params ...interface{}) (Result, error) {
return db.execOne(ctx, query, params...)
}
func (db *baseDB) execOne(c context.Context, query interface{}, params ...interface{}) (Result, error) {
res, err := db.ExecContext(c, query, params...)
if err != nil {
return nil, err
}
if err := internal.AssertOneRow(res.RowsAffected()); err != nil {
return nil, err
}
return res, nil
}
// Query executes a query that returns rows, typically a SELECT.
// The params are for any placeholders in the query.
func (db *baseDB) Query(model, query interface{}, params ...interface{}) (res Result, err error) {
return db.query(db.db.Context(), model, query, params...)
}
func (db *baseDB) QueryContext(c context.Context, model, query interface{}, params ...interface{}) (Result, error) {
return db.query(c, model, query, params...)
}
func (db *baseDB) query(ctx context.Context, model, query interface{}, params ...interface{}) (Result, error) {
wb := pool.GetWriteBuffer()
defer pool.PutWriteBuffer(wb)
if err := writeQueryMsg(wb, db.fmter, query, params...); err != nil {
return nil, err
}
ctx, evt, err := db.beforeQuery(ctx, db.db, model, query, params, wb.Query())
if err != nil {
return nil, err
}
var res Result
var lastErr error
for attempt := 0; attempt <= db.opt.MaxRetries; attempt++ {
if attempt > 0 {
if err := internal.Sleep(ctx, db.retryBackoff(attempt-1)); err != nil {
return nil, err
}
}
lastErr = db.withConn(ctx, func(ctx context.Context, cn *pool.Conn) error {
res, err = db.simpleQueryData(ctx, cn, model, wb)
return err
})
if !db.shouldRetry(lastErr) {
break
}
}
if err := db.afterQuery(ctx, evt, res, lastErr); err != nil {
return nil, err
}
return res, lastErr
}
// QueryOne acts like Query, but query must return only one row. It
// returns ErrNoRows error when query returns zero rows or
// ErrMultiRows when query returns multiple rows.
func (db *baseDB) QueryOne(model, query interface{}, params ...interface{}) (Result, error) {
return db.queryOne(db.db.Context(), model, query, params...)
}
func (db *baseDB) QueryOneContext(
ctx context.Context, model, query interface{}, params ...interface{},
) (Result, error) {
return db.queryOne(ctx, model, query, params...)
}
func (db *baseDB) queryOne(ctx context.Context, model, query interface{}, params ...interface{}) (Result, error) {
res, err := db.QueryContext(ctx, model, query, params...)
if err != nil {
return nil, err
}
if err := internal.AssertOneRow(res.RowsAffected()); err != nil {
return nil, err
}
return res, nil
}
// CopyFrom copies data from reader to a table.
func (db *baseDB) CopyFrom(r io.Reader, query interface{}, params ...interface{}) (res Result, err error) {
c := db.db.Context()
err = db.withConn(c, func(c context.Context, cn *pool.Conn) error {
res, err = db.copyFrom(c, cn, r, query, params...)
return err
})
return res, err
}
// TODO: don't get/put conn in the pool.
func (db *baseDB) copyFrom(
ctx context.Context, cn *pool.Conn, r io.Reader, query interface{}, params ...interface{},
) (res Result, err error) {
var evt *QueryEvent
wb := pool.GetWriteBuffer()
defer pool.PutWriteBuffer(wb)
if err := writeQueryMsg(wb, db.fmter, query, params...); err != nil {
return nil, err
}
var model interface{}
if len(params) > 0 {
model, _ = params[len(params)-1].(orm.TableModel)
}
ctx, evt, err = db.beforeQuery(ctx, db.db, model, query, params, wb.Query())
if err != nil {
return nil, err
}
// Note that afterQuery uses the err.
defer func() {
if afterQueryErr := db.afterQuery(ctx, evt, res, err); afterQueryErr != nil {
err = afterQueryErr
}
}()
err = cn.WithWriter(ctx, db.opt.WriteTimeout, func(wb *pool.WriteBuffer) error {
return writeQueryMsg(wb, db.fmter, query, params...)
})
if err != nil {
return nil, err
}
err = cn.WithReader(ctx, db.opt.ReadTimeout, readCopyInResponse)
if err != nil {
return nil, err
}
for {
err = cn.WithWriter(ctx, db.opt.WriteTimeout, func(wb *pool.WriteBuffer) error {
return writeCopyData(wb, r)
})
if err != nil {
if err == io.EOF {
break
}
return nil, err
}
}
err = cn.WithWriter(ctx, db.opt.WriteTimeout, func(wb *pool.WriteBuffer) error {
writeCopyDone(wb)
return nil
})
if err != nil {
return nil, err
}
err = cn.WithReader(ctx, db.opt.ReadTimeout, func(rd *pool.ReaderContext) error {
res, err = readReadyForQuery(rd)
return err
})
if err != nil {
return nil, err
}
return res, nil
}
// CopyTo copies data from a table to writer.
func (db *baseDB) CopyTo(w io.Writer, query interface{}, params ...interface{}) (res Result, err error) {
c := db.db.Context()
err = db.withConn(c, func(c context.Context, cn *pool.Conn) error {
res, err = db.copyTo(c, cn, w, query, params...)
return err
})
return res, err
}
func (db *baseDB) copyTo(
ctx context.Context, cn *pool.Conn, w io.Writer, query interface{}, params ...interface{},
) (res Result, err error) {
var evt *QueryEvent
wb := pool.GetWriteBuffer()
defer pool.PutWriteBuffer(wb)
if err := writeQueryMsg(wb, db.fmter, query, params...); err != nil {
return nil, err
}
var model interface{}
if len(params) > 0 {
model, _ = params[len(params)-1].(orm.TableModel)
}
ctx, evt, err = db.beforeQuery(ctx, db.db, model, query, params, wb.Query())
if err != nil {
return nil, err
}
// Note that afterQuery uses the err.
defer func() {
if afterQueryErr := db.afterQuery(ctx, evt, res, err); afterQueryErr != nil {
err = afterQueryErr
}
}()
err = cn.WithWriter(ctx, db.opt.WriteTimeout, func(wb *pool.WriteBuffer) error {
return writeQueryMsg(wb, db.fmter, query, params...)
})
if err != nil {
return nil, err
}
err = cn.WithReader(ctx, db.opt.ReadTimeout, func(rd *pool.ReaderContext) error {
err := readCopyOutResponse(rd)
if err != nil {
return err
}
res, err = readCopyData(rd, w)
return err
})
if err != nil {
return nil, err
}
return res, nil
}
// Ping verifies a connection to the database is still alive,
// establishing a connection if necessary.
func (db *baseDB) Ping(ctx context.Context) error {
_, err := db.ExecContext(ctx, "SELECT 1")
return err
}
// Model returns new query for the model.
func (db *baseDB) Model(model ...interface{}) *Query {
return orm.NewQuery(db.db, model...)
}
func (db *baseDB) ModelContext(c context.Context, model ...interface{}) *Query {
return orm.NewQueryContext(c, db.db, model...)
}
func (db *baseDB) Formatter() orm.QueryFormatter {
return db.fmter
}
func (db *baseDB) cancelRequest(processID, secretKey int32) error {
c := context.TODO()
cn, err := db.pool.NewConn(c)
if err != nil {
return err
}
defer func() {
_ = db.pool.CloseConn(cn)
}()
return cn.WithWriter(c, db.opt.WriteTimeout, func(wb *pool.WriteBuffer) error {
writeCancelRequestMsg(wb, processID, secretKey)
return nil
})
}
func (db *baseDB) simpleQuery(
c context.Context, cn *pool.Conn, wb *pool.WriteBuffer,
) (*result, error) {
if err := cn.WriteBuffer(c, db.opt.WriteTimeout, wb); err != nil {
return nil, err
}
var res *result
if err := cn.WithReader(c, db.opt.ReadTimeout, func(rd *pool.ReaderContext) error {
var err error
res, err = readSimpleQuery(rd)
return err
}); err != nil {
return nil, err
}
return res, nil
}
func (db *baseDB) simpleQueryData(
c context.Context, cn *pool.Conn, model interface{}, wb *pool.WriteBuffer,
) (*result, error) {
if err := cn.WriteBuffer(c, db.opt.WriteTimeout, wb); err != nil {
return nil, err
}
var res *result
if err := cn.WithReader(c, db.opt.ReadTimeout, func(rd *pool.ReaderContext) error {
var err error
res, err = readSimpleQueryData(c, rd, model)
return err
}); err != nil {
return nil, err
}
return res, nil
}
// Prepare creates a prepared statement for later queries or
// executions. Multiple queries or executions may be run concurrently
// from the returned statement.
func (db *baseDB) Prepare(q string) (*Stmt, error) {
return prepareStmt(db.withPool(pool.NewStickyConnPool(db.pool)), q)
}
func (db *baseDB) prepare(
c context.Context, cn *pool.Conn, q string,
) (string, []types.ColumnInfo, error) {
name := cn.NextID()
err := cn.WithWriter(c, db.opt.WriteTimeout, func(wb *pool.WriteBuffer) error {
writeParseDescribeSyncMsg(wb, name, q)
return nil
})
if err != nil {
return "", nil, err
}
var columns []types.ColumnInfo
err = cn.WithReader(c, db.opt.ReadTimeout, func(rd *pool.ReaderContext) error {
columns, err = readParseDescribeSync(rd)
return err
})
if err != nil {
return "", nil, err
}
return name, columns, nil
}
func (db *baseDB) closeStmt(c context.Context, cn *pool.Conn, name string) error {
err := cn.WithWriter(c, db.opt.WriteTimeout, func(wb *pool.WriteBuffer) error {
writeCloseMsg(wb, name)
writeFlushMsg(wb)
return nil
})
if err != nil {
return err
}
err = cn.WithReader(c, db.opt.ReadTimeout, readCloseCompleteMsg)
return err
}

142
vendor/github.com/go-pg/pg/v10/db.go generated vendored
View file

@ -1,142 +0,0 @@
package pg
import (
"context"
"fmt"
"time"
"github.com/go-pg/pg/v10/internal/pool"
"github.com/go-pg/pg/v10/orm"
)
// Connect connects to a database using provided options.
//
// The returned DB is safe for concurrent use by multiple goroutines
// and maintains its own connection pool.
func Connect(opt *Options) *DB {
opt.init()
return newDB(
context.Background(),
&baseDB{
opt: opt,
pool: newConnPool(opt),
fmter: orm.NewFormatter(),
},
)
}
func newDB(ctx context.Context, baseDB *baseDB) *DB {
db := &DB{
baseDB: baseDB.clone(),
ctx: ctx,
}
db.baseDB.db = db
return db
}
// DB is a database handle representing a pool of zero or more
// underlying connections. It's safe for concurrent use by multiple
// goroutines.
type DB struct {
*baseDB
ctx context.Context
}
var _ orm.DB = (*DB)(nil)
func (db *DB) String() string {
return fmt.Sprintf("DB<Addr=%q%s>", db.opt.Addr, db.fmter)
}
// Options returns read-only Options that were used to connect to the DB.
func (db *DB) Options() *Options {
return db.opt
}
// Context returns DB context.
func (db *DB) Context() context.Context {
return db.ctx
}
// WithContext returns a copy of the DB that uses the ctx.
func (db *DB) WithContext(ctx context.Context) *DB {
return newDB(ctx, db.baseDB)
}
// WithTimeout returns a copy of the DB that uses d as the read/write timeout.
func (db *DB) WithTimeout(d time.Duration) *DB {
return newDB(db.ctx, db.baseDB.WithTimeout(d))
}
// WithParam returns a copy of the DB that replaces the param with the value
// in queries.
func (db *DB) WithParam(param string, value interface{}) *DB {
return newDB(db.ctx, db.baseDB.WithParam(param, value))
}
// Listen listens for notifications sent with NOTIFY command.
func (db *DB) Listen(ctx context.Context, channels ...string) *Listener {
ln := &Listener{
db: db,
}
ln.init()
_ = ln.Listen(ctx, channels...)
return ln
}
// Conn represents a single database connection rather than a pool of database
// connections. Prefer running queries from DB unless there is a specific
// need for a continuous single database connection.
//
// A Conn must call Close to return the connection to the database pool
// and may do so concurrently with a running query.
//
// After a call to Close, all operations on the connection fail.
type Conn struct {
*baseDB
ctx context.Context
}
var _ orm.DB = (*Conn)(nil)
// Conn returns a single connection from the connection pool.
// Queries run on the same Conn will be run in the same database session.
//
// Every Conn must be returned to the database pool after use by
// calling Conn.Close.
func (db *DB) Conn() *Conn {
return newConn(db.ctx, db.baseDB.withPool(pool.NewStickyConnPool(db.pool)))
}
func newConn(ctx context.Context, baseDB *baseDB) *Conn {
conn := &Conn{
baseDB: baseDB,
ctx: ctx,
}
conn.baseDB.db = conn
return conn
}
// Context returns DB context.
func (db *Conn) Context() context.Context {
if db.ctx != nil {
return db.ctx
}
return context.Background()
}
// WithContext returns a copy of the DB that uses the ctx.
func (db *Conn) WithContext(ctx context.Context) *Conn {
return newConn(ctx, db.baseDB)
}
// WithTimeout returns a copy of the DB that uses d as the read/write timeout.
func (db *Conn) WithTimeout(d time.Duration) *Conn {
return newConn(db.ctx, db.baseDB.WithTimeout(d))
}
// WithParam returns a copy of the DB that replaces the param with the value
// in queries.
func (db *Conn) WithParam(param string, value interface{}) *Conn {
return newConn(db.ctx, db.baseDB.WithParam(param, value))
}

View file

@ -1,4 +0,0 @@
/*
pg provides PostgreSQL client.
*/
package pg

View file

@ -1,69 +0,0 @@
package pg
import (
"net"
"github.com/go-pg/pg/v10/internal"
)
// ErrNoRows is returned by QueryOne and ExecOne when query returned zero rows
// but at least one row is expected.
var ErrNoRows = internal.ErrNoRows
// ErrMultiRows is returned by QueryOne and ExecOne when query returned
// multiple rows but exactly one row is expected.
var ErrMultiRows = internal.ErrMultiRows
// Error represents an error returned by PostgreSQL server
// using PostgreSQL ErrorResponse protocol.
//
// https://www.postgresql.org/docs/10/static/protocol-message-formats.html
type Error interface {
error
// Field returns a string value associated with an error field.
//
// https://www.postgresql.org/docs/10/static/protocol-error-fields.html
Field(field byte) string
// IntegrityViolation reports whether an error is a part of
// Integrity Constraint Violation class of errors.
//
// https://www.postgresql.org/docs/10/static/errcodes-appendix.html
IntegrityViolation() bool
}
var _ Error = (*internal.PGError)(nil)
func isBadConn(err error, allowTimeout bool) bool {
if err == nil {
return false
}
if _, ok := err.(internal.Error); ok {
return false
}
if pgErr, ok := err.(Error); ok {
switch pgErr.Field('V') {
case "FATAL", "PANIC":
return true
}
switch pgErr.Field('C') {
case "25P02", // current transaction is aborted
"57014": // canceling statement due to user request
return true
}
return false
}
if allowTimeout {
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
return !netErr.Temporary()
}
}
return true
}
//------------------------------------------------------------------------------
type timeoutError interface {
Timeout() bool
}

View file

@ -1,24 +0,0 @@
module github.com/go-pg/pg/v10
go 1.11
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-pg/zerochecker v0.2.0
github.com/golang/protobuf v1.4.3 // indirect
github.com/google/go-cmp v0.5.5 // indirect
github.com/jinzhu/inflection v1.0.0
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/onsi/ginkgo v1.14.2
github.com/onsi/gomega v1.10.3
github.com/stretchr/testify v1.7.0
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc
github.com/vmihailenco/bufpool v0.1.11
github.com/vmihailenco/msgpack/v5 v5.3.1
github.com/vmihailenco/tagparser v0.1.2
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b // indirect
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7 // indirect
google.golang.org/protobuf v1.25.0 // indirect
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f
mellium.im/sasl v0.2.1
)

154
vendor/github.com/go-pg/pg/v10/go.sum generated vendored
View file

@ -1,154 +0,0 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
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/go-pg/zerochecker v0.2.0 h1:pp7f72c3DobMWOb2ErtZsnrPaSvHd2W4o9//8HtF4mU=
github.com/go-pg/zerochecker v0.2.0/go.mod h1:NJZ4wKL0NmTtz0GKCoJ8kym6Xn/EQzXRl2OnAe7MmDo=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/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/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
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/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
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/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
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.14.2 h1:8mVmC9kjFFmA8H4pKMUhcblgifdkOIXPvbhN1T36q1M=
github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
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.3 h1:gph6h/qe9GSUw1NhH1gp+qb+h8rXD8Cy60Z32Qw3ELA=
github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo=
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs=
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/v5 v5.3.1 h1:0i85a4dsZh8mC//wmyyTEzidDLPQfQAxZIOLtafGbFY=
github.com/vmihailenco/msgpack/v5 v5.3.1/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc=
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/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
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-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b h1:7mWr3k41Qtv8XlltBkDkl8LoP3mpSgBW8BUoxtEdbXg=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7 h1:iGu644GcxtEcrInvDsQRCwJjtCIOlT2V7IRt6ah2Whw=
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
mellium.im/sasl v0.2.1 h1:nspKSRg7/SyO0cRGY71OkfHab8tf9kCts6a6oTDut0w=
mellium.im/sasl v0.2.1/go.mod h1:ROaEDLQNuf9vjKqE1SrAfnsobm2YKXT1gnN1uDp1PjQ=

View file

@ -1,139 +0,0 @@
package pg
import (
"context"
"fmt"
"time"
"github.com/go-pg/pg/v10/orm"
)
type (
BeforeScanHook = orm.BeforeScanHook
AfterScanHook = orm.AfterScanHook
AfterSelectHook = orm.AfterSelectHook
BeforeInsertHook = orm.BeforeInsertHook
AfterInsertHook = orm.AfterInsertHook
BeforeUpdateHook = orm.BeforeUpdateHook
AfterUpdateHook = orm.AfterUpdateHook
BeforeDeleteHook = orm.BeforeDeleteHook
AfterDeleteHook = orm.AfterDeleteHook
)
//------------------------------------------------------------------------------
type dummyFormatter struct{}
func (dummyFormatter) FormatQuery(b []byte, query string, params ...interface{}) []byte {
return append(b, query...)
}
// QueryEvent ...
type QueryEvent struct {
StartTime time.Time
DB orm.DB
Model interface{}
Query interface{}
Params []interface{}
fmtedQuery []byte
Result Result
Err error
Stash map[interface{}]interface{}
}
// QueryHook ...
type QueryHook interface {
BeforeQuery(context.Context, *QueryEvent) (context.Context, error)
AfterQuery(context.Context, *QueryEvent) error
}
// UnformattedQuery returns the unformatted query of a query event.
// The query is only valid until the query Result is returned to the user.
func (e *QueryEvent) UnformattedQuery() ([]byte, error) {
return queryString(e.Query)
}
func queryString(query interface{}) ([]byte, error) {
switch query := query.(type) {
case orm.TemplateAppender:
return query.AppendTemplate(nil)
case string:
return dummyFormatter{}.FormatQuery(nil, query), nil
default:
return nil, fmt.Errorf("pg: can't append %T", query)
}
}
// FormattedQuery returns the formatted query of a query event.
// The query is only valid until the query Result is returned to the user.
func (e *QueryEvent) FormattedQuery() ([]byte, error) {
return e.fmtedQuery, nil
}
// AddQueryHook adds a hook into query processing.
func (db *baseDB) AddQueryHook(hook QueryHook) {
db.queryHooks = append(db.queryHooks, hook)
}
func (db *baseDB) beforeQuery(
ctx context.Context,
ormDB orm.DB,
model, query interface{},
params []interface{},
fmtedQuery []byte,
) (context.Context, *QueryEvent, error) {
if len(db.queryHooks) == 0 {
return ctx, nil, nil
}
event := &QueryEvent{
StartTime: time.Now(),
DB: ormDB,
Model: model,
Query: query,
Params: params,
fmtedQuery: fmtedQuery,
}
for i, hook := range db.queryHooks {
var err error
ctx, err = hook.BeforeQuery(ctx, event)
if err != nil {
if err := db.afterQueryFromIndex(ctx, event, i); err != nil {
return ctx, nil, err
}
return ctx, nil, err
}
}
return ctx, event, nil
}
func (db *baseDB) afterQuery(
ctx context.Context,
event *QueryEvent,
res Result,
err error,
) error {
if event == nil {
return nil
}
event.Err = err
event.Result = res
return db.afterQueryFromIndex(ctx, event, len(db.queryHooks)-1)
}
func (db *baseDB) afterQueryFromIndex(ctx context.Context, event *QueryEvent, hookIndex int) error {
for ; hookIndex >= 0; hookIndex-- {
if err := db.queryHooks[hookIndex].AfterQuery(ctx, event); err != nil {
return err
}
}
return nil
}
func copyQueryHooks(s []QueryHook) []QueryHook {
return s[:len(s):len(s)]
}

View file

@ -1,26 +0,0 @@
package internal
import (
"context"
"time"
)
type UndoneContext struct {
context.Context
}
func UndoContext(ctx context.Context) UndoneContext {
return UndoneContext{Context: ctx}
}
func (UndoneContext) Deadline() (deadline time.Time, ok bool) {
return time.Time{}, false
}
func (UndoneContext) Done() <-chan struct{} {
return nil
}
func (UndoneContext) Err() error {
return nil
}

View file

@ -1,61 +0,0 @@
package internal
import (
"fmt"
)
var (
ErrNoRows = Errorf("pg: no rows in result set")
ErrMultiRows = Errorf("pg: multiple rows in result set")
)
type Error struct {
s string
}
func Errorf(s string, args ...interface{}) Error {
return Error{s: fmt.Sprintf(s, args...)}
}
func (err Error) Error() string {
return err.s
}
type PGError struct {
m map[byte]string
}
func NewPGError(m map[byte]string) PGError {
return PGError{
m: m,
}
}
func (err PGError) Field(k byte) string {
return err.m[k]
}
func (err PGError) IntegrityViolation() bool {
switch err.Field('C') {
case "23000", "23001", "23502", "23503", "23505", "23514", "23P01":
return true
default:
return false
}
}
func (err PGError) Error() string {
return fmt.Sprintf("%s #%s %s",
err.Field('S'), err.Field('C'), err.Field('M'))
}
func AssertOneRow(l int) error {
switch {
case l == 0:
return ErrNoRows
case l > 1:
return ErrMultiRows
default:
return nil
}
}

View file

@ -1,27 +0,0 @@
/*
internal is a private internal package.
*/
package internal
import (
"math/rand"
"time"
)
func RetryBackoff(retry int, minBackoff, maxBackoff time.Duration) time.Duration {
if retry < 0 {
panic("not reached")
}
if minBackoff == 0 {
return 0
}
d := minBackoff << uint(retry)
d = minBackoff + time.Duration(rand.Int63n(int64(d)))
if d > maxBackoff || d < minBackoff {
d = maxBackoff
}
return d
}

View file

@ -1,28 +0,0 @@
package internal
import (
"context"
"fmt"
"log"
"os"
)
var Warn = log.New(os.Stderr, "WARN: pg: ", log.LstdFlags)
var Deprecated = log.New(os.Stderr, "DEPRECATED: pg: ", log.LstdFlags)
type Logging interface {
Printf(ctx context.Context, format string, v ...interface{})
}
type logger struct {
log *log.Logger
}
func (l *logger) Printf(ctx context.Context, format string, v ...interface{}) {
_ = l.log.Output(2, fmt.Sprintf(format, v...))
}
var Logger Logging = &logger{
log: log.New(os.Stderr, "pg: ", log.LstdFlags|log.Lshortfile),
}

View file

@ -1,141 +0,0 @@
package parser
import (
"bytes"
"strconv"
"github.com/go-pg/pg/v10/internal"
)
type Parser struct {
b []byte
i int
}
func New(b []byte) *Parser {
return &Parser{
b: b,
}
}
func NewString(s string) *Parser {
return New(internal.StringToBytes(s))
}
func (p *Parser) Valid() bool {
return p.i < len(p.b)
}
func (p *Parser) Bytes() []byte {
return p.b[p.i:]
}
func (p *Parser) Read() byte {
if p.Valid() {
c := p.b[p.i]
p.Advance()
return c
}
return 0
}
func (p *Parser) Peek() byte {
if p.Valid() {
return p.b[p.i]
}
return 0
}
func (p *Parser) Advance() {
p.i++
}
func (p *Parser) Skip(skip byte) bool {
if p.Peek() == skip {
p.Advance()
return true
}
return false
}
func (p *Parser) SkipBytes(skip []byte) bool {
if len(skip) > len(p.b[p.i:]) {
return false
}
if !bytes.Equal(p.b[p.i:p.i+len(skip)], skip) {
return false
}
p.i += len(skip)
return true
}
func (p *Parser) ReadSep(sep byte) ([]byte, bool) {
ind := bytes.IndexByte(p.b[p.i:], sep)
if ind == -1 {
b := p.b[p.i:]
p.i = len(p.b)
return b, false
}
b := p.b[p.i : p.i+ind]
p.i += ind + 1
return b, true
}
func (p *Parser) ReadIdentifier() (string, bool) {
if p.i < len(p.b) && p.b[p.i] == '(' {
s := p.i + 1
if ind := bytes.IndexByte(p.b[s:], ')'); ind != -1 {
b := p.b[s : s+ind]
p.i = s + ind + 1
return internal.BytesToString(b), false
}
}
ind := len(p.b) - p.i
var alpha bool
for i, c := range p.b[p.i:] {
if isNum(c) {
continue
}
if isAlpha(c) || (i > 0 && alpha && c == '_') {
alpha = true
continue
}
ind = i
break
}
if ind == 0 {
return "", false
}
b := p.b[p.i : p.i+ind]
p.i += ind
return internal.BytesToString(b), !alpha
}
func (p *Parser) ReadNumber() int {
ind := len(p.b) - p.i
for i, c := range p.b[p.i:] {
if !isNum(c) {
ind = i
break
}
}
if ind == 0 {
return 0
}
n, err := strconv.Atoi(string(p.b[p.i : p.i+ind]))
if err != nil {
panic(err)
}
p.i += ind
return n
}
func isNum(c byte) bool {
return c >= '0' && c <= '9'
}
func isAlpha(c byte) bool {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
}

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