forked from mirrors/gotosocial
Compare commits
16 commits
main
...
nullpointe
Author | SHA1 | Date | |
---|---|---|---|
b3194f1fee | |||
af29c06290 | |||
|
0ddc2edf19 | ||
|
3920bc87d1 | ||
|
4b05dcde43 | ||
|
9df4d38c43 | ||
|
be3718f6e4 | ||
|
517829ae6a | ||
|
0f812746b7 | ||
|
303a6a6b1d | ||
|
cc69250bbe | ||
|
0242f03d36 | ||
|
aaa5985d7d | ||
|
c1375ca5c1 | ||
|
6f4ae8f58d | ||
|
7f1f2b80ea |
168 changed files with 3058 additions and 4926 deletions
10
README.md
10
README.md
|
@ -91,13 +91,13 @@ For a detailed view on what's implemented and what's not, and progress made towa
|
|||
|
||||
The Mastodon API has become the de facto standard for client communication with federated servers, so GoToSocial has implemented and extended the API with custom functionality.
|
||||
|
||||
In short, this means full support for modern, beautiful apps like [Tusky](https://tusky.app/) and [Semaphore](https://semaphore.social/).
|
||||
Though most apps that implement the Mastodon API should work, GoToSocial works reliably with beautiful apps like:
|
||||
|
||||
Tusky | Semaphore
|
||||
:-----------------------------------------------------------:|:------------------------------------------------------------------:
|
||||
 | 
|
||||
* [Tusky](https://tusky.app/) for Android
|
||||
* [Semaphore](https://semaphore.social/) in the browser
|
||||
* [Feditext](https://fedi.software/@Feditext) (beta) on iOS, iPadOS and macOS
|
||||
|
||||
If you're used to using Mastodon with Tusky or Semaphore, you'll find using GoToSocial a breeze.
|
||||
If you've used Mastodon with any of these apps before, you'll find using GoToSocial a breeze.
|
||||
|
||||
### Granular post settings
|
||||
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 673 KiB |
Binary file not shown.
Before Width: | Height: | Size: 624 KiB |
|
@ -37,7 +37,7 @@ Then you should have already created database `gotosocial` in Postgres, and give
|
|||
The psql commands to do this will look something like:
|
||||
|
||||
```psql
|
||||
create database gotosocial with locale C.UTF-8 template template0;
|
||||
create database gotosocial with locale 'C.UTF-8' template template0;
|
||||
create user gotosocial with password 'some_really_good_password';
|
||||
grant all privileges on database gotosocial to gotosocial;
|
||||
```
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
## Where's the user interface?
|
||||
|
||||
GoToSocial is just a bare server for the most part and is designed to be used thru external applications. [Semaphore](https://semaphore.social/) and [Tusky](https://tusky.app/) are the best-supported, but anything that supports the Mastodon API should work, other than the features GoToSocial doesn't yet have. Permalinks and profile pages are served directly through GoToSocial as well as the settings panel, but most interaction goes through the apps.
|
||||
GoToSocial is just a bare server for the most part and is designed to be used thru external applications. [Semaphore](https://semaphore.social/) in the browser, [Tusky](https://tusky.app/) for Android and [Feditext](https://fedi.software/@Feditext) for iOS, iPadOS and macOS are the best-supported. Anything that supports the Mastodon API should work, other than the features GoToSocial doesn't yet have. Permalinks and profile pages are served directly through GoToSocial as well as the settings panel, but most interaction goes through the apps.
|
||||
|
||||
## Why aren't my posts showing up on my profile page?
|
||||
|
||||
|
|
|
@ -78,7 +78,7 @@ You can use the GoToSocial binary to also create and promote your user account.
|
|||
|
||||
## Login
|
||||
|
||||
You should now be able to log in to your instance using the email address and password of the account you just created. We recommend using [Semaphore](https://semaphore.social) or [Tusky](https://tusky.app) for this.
|
||||
You should now be able to log in to your instance using the email address and password of the account you just created.
|
||||
|
||||
## (Optional) Enable the systemd service
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ Since GoToSocial is still in alpha, there are plenty of bugs. We use [GitHub iss
|
|||
|
||||
### Client App Issues
|
||||
|
||||
GoToSocial works great with Tusky and Semaphore, but some other client applications still need work or have issues connecting to GoToSocial. We're tracking them [right here](https://github.com/superseriousbusiness/gotosocial/projects/5). It's our goal to make any app that's compatible with the Mastodon API work seamlessly with GoToSocial.
|
||||
GoToSocial works great with Tusky, Semaphore and Feditext, but some other client applications still need work or have issues connecting to GoToSocial. We're tracking them [right here](https://github.com/superseriousbusiness/gotosocial/projects/5). It's our goal to make any app that's compatible with the Mastodon API work seamlessly with GoToSocial.
|
||||
|
||||
### Federation Issues
|
||||
|
||||
|
|
22
go.mod
22
go.mod
|
@ -5,7 +5,7 @@ go 1.20
|
|||
require (
|
||||
codeberg.org/gruf/go-bytesize v1.0.2
|
||||
codeberg.org/gruf/go-byteutil v1.1.2
|
||||
codeberg.org/gruf/go-cache/v3 v3.5.5
|
||||
codeberg.org/gruf/go-cache/v3 v3.5.6
|
||||
codeberg.org/gruf/go-debug v1.3.0
|
||||
codeberg.org/gruf/go-errors/v2 v2.2.0
|
||||
codeberg.org/gruf/go-fastcopy v1.1.2
|
||||
|
@ -18,7 +18,7 @@ require (
|
|||
codeberg.org/gruf/go-store/v2 v2.2.2
|
||||
github.com/DmitriyVTitov/size v1.5.0
|
||||
github.com/KimMachineGun/automemlimit v0.2.6
|
||||
github.com/abema/go-mp4 v0.11.0
|
||||
github.com/abema/go-mp4 v0.12.0
|
||||
github.com/buckket/go-blurhash v1.1.0
|
||||
github.com/coreos/go-oidc/v3 v3.6.0
|
||||
github.com/disintegration/imaging v1.6.2
|
||||
|
@ -28,7 +28,6 @@ require (
|
|||
github.com/gin-gonic/gin v1.9.1
|
||||
github.com/go-fed/httpsig v1.1.0
|
||||
github.com/go-playground/form/v4 v4.2.1
|
||||
github.com/go-playground/validator/v10 v10.14.1
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/gorilla/feeds v1.1.1
|
||||
github.com/gorilla/websocket v1.5.0
|
||||
|
@ -46,7 +45,7 @@ require (
|
|||
github.com/superseriousbusiness/activity v1.4.0-gts
|
||||
github.com/superseriousbusiness/exif-terminator v0.5.0
|
||||
github.com/superseriousbusiness/oauth2/v4 v4.3.2-SSB.0.20230227143000-f4900831d6c8
|
||||
github.com/tdewolff/minify/v2 v2.12.7
|
||||
github.com/tdewolff/minify/v2 v2.12.8
|
||||
github.com/ulule/limiter/v3 v3.11.2
|
||||
github.com/uptrace/bun v1.1.14
|
||||
github.com/uptrace/bun/dialect/pgdialect v1.1.14
|
||||
|
@ -60,12 +59,12 @@ require (
|
|||
go.opentelemetry.io/otel/sdk v1.16.0
|
||||
go.opentelemetry.io/otel/trace v1.16.0
|
||||
go.uber.org/automaxprocs v1.5.3
|
||||
golang.org/x/crypto v0.11.0
|
||||
golang.org/x/crypto v0.12.0
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1
|
||||
golang.org/x/image v0.9.0
|
||||
golang.org/x/net v0.12.0
|
||||
golang.org/x/oauth2 v0.10.0
|
||||
golang.org/x/text v0.11.0
|
||||
golang.org/x/image v0.11.0
|
||||
golang.org/x/net v0.14.0
|
||||
golang.org/x/oauth2 v0.11.0
|
||||
golang.org/x/text v0.12.0
|
||||
gopkg.in/mcuadros/go-syslog.v2 v2.3.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
modernc.org/sqlite v1.24.0
|
||||
|
@ -108,6 +107,7 @@ require (
|
|||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.14.1 // indirect
|
||||
github.com/go-xmlfmt/xmlfmt v0.0.0-20211206191508-7fd73a941850 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/godbus/dbus/v5 v5.0.4 // indirect
|
||||
|
@ -152,7 +152,7 @@ require (
|
|||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/subosito/gotenv v1.4.2 // indirect
|
||||
github.com/superseriousbusiness/go-jpeg-image-structure/v2 v2.0.0-20220321154430-d89a106fdabe // indirect
|
||||
github.com/tdewolff/parse/v2 v2.6.6 // indirect
|
||||
github.com/tdewolff/parse/v2 v2.6.7 // indirect
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.11 // indirect
|
||||
|
@ -165,7 +165,7 @@ require (
|
|||
go.opentelemetry.io/proto/otlp v0.19.0 // indirect
|
||||
golang.org/x/arch v0.3.0 // indirect
|
||||
golang.org/x/mod v0.10.0 // indirect
|
||||
golang.org/x/sys v0.10.0 // indirect
|
||||
golang.org/x/sys v0.11.0 // indirect
|
||||
golang.org/x/tools v0.6.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
|
||||
|
|
46
go.sum
46
go.sum
|
@ -48,8 +48,8 @@ codeberg.org/gruf/go-bytesize v1.0.2/go.mod h1:n/GU8HzL9f3UNp/mUKyr1qVmTlj7+xacp
|
|||
codeberg.org/gruf/go-byteutil v1.0.0/go.mod h1:cWM3tgMCroSzqoBXUXMhvxTxYJp+TbCr6ioISRY5vSU=
|
||||
codeberg.org/gruf/go-byteutil v1.1.2 h1:TQLZtTxTNca9xEfDIndmo7nBYxeS94nrv/9DS3Nk5Tw=
|
||||
codeberg.org/gruf/go-byteutil v1.1.2/go.mod h1:cWM3tgMCroSzqoBXUXMhvxTxYJp+TbCr6ioISRY5vSU=
|
||||
codeberg.org/gruf/go-cache/v3 v3.5.5 h1:Ce7odyvr8oF6h49LSjPL7AZs2QGyKMN9BPkgKcfR0BA=
|
||||
codeberg.org/gruf/go-cache/v3 v3.5.5/go.mod h1:NbsGQUgEdNFd631WSasvCHIVAaY9ovuiSeoBwtsIeDc=
|
||||
codeberg.org/gruf/go-cache/v3 v3.5.6 h1:TJnNOuij5DF/ZK9pDB61SlYzxidRQeYjYYW3dfFSznc=
|
||||
codeberg.org/gruf/go-cache/v3 v3.5.6/go.mod h1:NbsGQUgEdNFd631WSasvCHIVAaY9ovuiSeoBwtsIeDc=
|
||||
codeberg.org/gruf/go-debug v1.3.0 h1:PIRxQiWUFKtGOGZFdZ3Y0pqyfI0Xr87j224IYe2snZs=
|
||||
codeberg.org/gruf/go-debug v1.3.0/go.mod h1:N+vSy9uJBQgpQcJUqjctvqFz7tBHJf+S/PIjLILzpLg=
|
||||
codeberg.org/gruf/go-errors/v2 v2.0.0/go.mod h1:ZRhbdhvgoUA3Yw6e56kd9Ox984RrvbEFC2pOXyHDJP4=
|
||||
|
@ -92,8 +92,8 @@ github.com/DmitriyVTitov/size v1.5.0/go.mod h1:le6rNI4CoLQV1b9gzp1+3d7hMAD/uu2Qc
|
|||
github.com/KimMachineGun/automemlimit v0.2.6 h1:tQFriVTcIteUkV5EgU9iz03eDY36T8JU5RAjP2r6Kt0=
|
||||
github.com/KimMachineGun/automemlimit v0.2.6/go.mod h1:pJhTW/nWJMj6SnWSU2TEKSlCaM+1N5Mej+IfS/5/Ol0=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/abema/go-mp4 v0.11.0 h1:XJfpSmEdUBMvWnDKIiNznLGZPpeDxJ/qDdN6Ust1ADk=
|
||||
github.com/abema/go-mp4 v0.11.0/go.mod h1:vPl9t5ZK7K0x68jh12/+ECWBCXoWuIDtNgPtU2f04ws=
|
||||
github.com/abema/go-mp4 v0.12.0 h1:XI9PPt1BpjB3wFl18oFiX6C99uesx7F/X13Z+ga8bYY=
|
||||
github.com/abema/go-mp4 v0.12.0/go.mod h1:vPl9t5ZK7K0x68jh12/+ECWBCXoWuIDtNgPtU2f04ws=
|
||||
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
|
||||
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
|
||||
github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
|
||||
|
@ -111,7 +111,6 @@ github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyY
|
|||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
||||
|
@ -149,7 +148,6 @@ 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/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
|
||||
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
||||
github.com/djherbis/atime v1.1.0/go.mod h1:28OF6Y8s3NQWwacXc5eZTsEsiMzp7LF8MbXE+XJPdBE=
|
||||
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
|
||||
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/dsoprea/go-exif/v2 v2.0.0-20200321225314-640175a69fe4/go.mod h1:Lm2lMM2zx8p4a34ZemkaUV95AnMl4ZvLbCUbwOvLC2E=
|
||||
|
@ -438,7 +436,6 @@ 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/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
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.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
|
@ -564,11 +561,10 @@ github.com/superseriousbusiness/go-jpeg-image-structure/v2 v2.0.0-20220321154430
|
|||
github.com/superseriousbusiness/go-jpeg-image-structure/v2 v2.0.0-20220321154430-d89a106fdabe/go.mod h1:gH4P6gN1V+wmIw5o97KGaa1RgXB/tVpC2UNzijhg3E4=
|
||||
github.com/superseriousbusiness/oauth2/v4 v4.3.2-SSB.0.20230227143000-f4900831d6c8 h1:nTIhuP157oOFcscuoK1kCme1xTeGIzztSw70lX9NrDQ=
|
||||
github.com/superseriousbusiness/oauth2/v4 v4.3.2-SSB.0.20230227143000-f4900831d6c8/go.mod h1:uYC/W92oVRJ49Vh1GcvTqpeFqHi+Ovrl2sMllQWRAEo=
|
||||
github.com/tdewolff/minify/v2 v2.12.7 h1:pBzz2tAfz5VghOXiQIsSta6srhmTeinQPjRDHWoumCA=
|
||||
github.com/tdewolff/minify/v2 v2.12.7/go.mod h1:ZRKTheiOGyLSK8hOZWWv+YoJAECzDivNgAlVYDHp/Ws=
|
||||
github.com/tdewolff/parse/v2 v2.6.6 h1:Yld+0CrKUJaCV78DL1G2nk3C9lKrxyRTux5aaK/AkDo=
|
||||
github.com/tdewolff/parse/v2 v2.6.6/go.mod h1:woz0cgbLwFdtbjJu8PIKxhW05KplTFQkOdX78o+Jgrs=
|
||||
github.com/tdewolff/test v1.0.7/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
|
||||
github.com/tdewolff/minify/v2 v2.12.8 h1:Q2BqOTmlMjoutkuD/OPCnJUpIqrzT3nRPkw+q+KpXS0=
|
||||
github.com/tdewolff/minify/v2 v2.12.8/go.mod h1:YRgk7CC21LZnbuke2fmYnCTq+zhCgpb0yJACOTUNJ1E=
|
||||
github.com/tdewolff/parse/v2 v2.6.7 h1:WrFllrqmzAcrKHzoYgMupqgUBIfBVOb0yscFzDf8bBg=
|
||||
github.com/tdewolff/parse/v2 v2.6.7/go.mod h1:XHDhaU6IBgsryfdnpzUXBlT6leW/l25yrFBTEb4eIyM=
|
||||
github.com/tdewolff/test v1.0.9 h1:SswqJCmeN4B+9gEAi/5uqT0qpi1y2/2O47V/1hhGZT0=
|
||||
github.com/tdewolff/test v1.0.9/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
|
||||
github.com/tidwall/btree v0.0.0-20191029221954-400434d76274 h1:G6Z6HvJuPjG6XfNGi/feOATzeJrfgTNJY+rGrHbA04E=
|
||||
|
@ -696,8 +692,8 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5y
|
|||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
||||
golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
|
||||
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
|
||||
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
|
||||
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
|
@ -713,8 +709,8 @@ golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnL
|
|||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.9.0 h1:QrzfX26snvCM20hIhBwuHI/ThTg18b/+kcKdXHvnR+g=
|
||||
golang.org/x/image v0.9.0/go.mod h1:jtrku+n79PfroUbvDdeUWMAI+heR786BofxrbiSF+J0=
|
||||
golang.org/x/image v0.11.0 h1:ds2RoQvBvYTiJkwpSFDwCcDFNX7DqjL2WsUgTNk0Ooo=
|
||||
golang.org/x/image v0.11.0/go.mod h1:bglhjqbqVuEb9e9+eNR45Jfu7D+T4Qan+NhQk8Ck2P8=
|
||||
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-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
|
@ -779,8 +775,8 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b
|
|||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
|
||||
golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
|
||||
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
|
||||
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
|
@ -792,8 +788,8 @@ golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ
|
|||
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8=
|
||||
golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI=
|
||||
golang.org/x/oauth2 v0.11.0 h1:vPL4xzxBM4niKCW6g9whtaWVXTJf1U5e4aZxxFx/gbU=
|
||||
golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk=
|
||||
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-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
|
@ -866,13 +862,13 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
|
||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
|
||||
golang.org/x/sys v0.11.0/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/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c=
|
||||
golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
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=
|
||||
|
@ -883,8 +879,8 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
|
||||
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
|
||||
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
|
|
|
@ -11,7 +11,7 @@ import (
|
|||
"github.com/stretchr/testify/suite"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/api/auth"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
)
|
||||
|
||||
type AuthAuthorizeTestSuite struct {
|
||||
|
@ -51,8 +51,8 @@ func (suite *AuthAuthorizeTestSuite) TestAccountAuthorizeHandler() {
|
|||
mutateUserAccount: func(user *gtsmodel.User, account *gtsmodel.Account) []string {
|
||||
user.ConfirmedAt = time.Now()
|
||||
user.Email = user.UnconfirmedEmail
|
||||
user.Approved = testrig.TrueBool()
|
||||
user.Disabled = testrig.TrueBool()
|
||||
user.Approved = util.Ptr(true)
|
||||
user.Disabled = util.Ptr(true)
|
||||
return []string{"confirmed_at", "email", "approved", "disabled"}
|
||||
},
|
||||
expectedStatusCode: http.StatusSeeOther,
|
||||
|
@ -63,8 +63,8 @@ func (suite *AuthAuthorizeTestSuite) TestAccountAuthorizeHandler() {
|
|||
mutateUserAccount: func(user *gtsmodel.User, account *gtsmodel.Account) []string {
|
||||
user.ConfirmedAt = time.Now()
|
||||
user.Email = user.UnconfirmedEmail
|
||||
user.Approved = testrig.TrueBool()
|
||||
user.Disabled = testrig.FalseBool()
|
||||
user.Approved = util.Ptr(true)
|
||||
user.Disabled = util.Ptr(false)
|
||||
account.SuspendedAt = time.Now()
|
||||
return []string{"confirmed_at", "email", "approved", "disabled"}
|
||||
},
|
||||
|
|
|
@ -87,7 +87,7 @@ func (m *Module) AccountCreatePOSTHandler(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
if err := validateCreateAccount(form); err != nil {
|
||||
if err := validateNormalizeCreateAccount(form); err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
@ -110,9 +110,10 @@ func (m *Module) AccountCreatePOSTHandler(c *gin.Context) {
|
|||
c.JSON(http.StatusOK, ti)
|
||||
}
|
||||
|
||||
// validateCreateAccount checks through all the necessary prerequisites for creating a new account,
|
||||
// validateNormalizeCreateAccount checks through all the necessary prerequisites for creating a new account,
|
||||
// according to the provided account create request. If the account isn't eligible, an error will be returned.
|
||||
func validateCreateAccount(form *apimodel.AccountCreateRequest) error {
|
||||
// Side effect: normalizes the provided language tag for the user's locale.
|
||||
func validateNormalizeCreateAccount(form *apimodel.AccountCreateRequest) error {
|
||||
if form == nil {
|
||||
return errors.New("form was nil")
|
||||
}
|
||||
|
@ -137,9 +138,11 @@ func validateCreateAccount(form *apimodel.AccountCreateRequest) error {
|
|||
return errors.New("agreement to terms and conditions not given")
|
||||
}
|
||||
|
||||
if err := validate.Language(form.Locale); err != nil {
|
||||
locale, err := validate.Language(form.Locale)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
form.Locale = locale
|
||||
|
||||
return validate.SignUpReason(form.Reason, config.GetAccountsReasonRequired())
|
||||
}
|
||||
|
|
|
@ -138,7 +138,7 @@ func (suite *ReportResolveTestSuite) TestReportResolve2() {
|
|||
testToken := suite.testTokens["admin_account"]
|
||||
testUser := suite.testUsers["admin_account"]
|
||||
testReportID := suite.testReports["local_account_2_report_remote_account_1"].ID
|
||||
var actionTakenComment *string = testrig.StringPtr("no action was taken, this is a frivolous report you boob")
|
||||
var actionTakenComment *string = util.Ptr("no action was taken, this is a frivolous report you boob")
|
||||
|
||||
report, err := suite.resolveReport(testAccount, testToken, testUser, testReportID, http.StatusOK, "", actionTakenComment)
|
||||
suite.NoError(err)
|
||||
|
|
|
@ -32,6 +32,7 @@ import (
|
|||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
)
|
||||
|
||||
|
@ -963,7 +964,7 @@ func (suite *ReportsGetTestSuite) TestReportsGetResolvedTargetAccount() {
|
|||
testAccount := suite.testAccounts["admin_account"]
|
||||
testToken := suite.testTokens["admin_account"]
|
||||
testUser := suite.testUsers["admin_account"]
|
||||
resolved := testrig.FalseBool()
|
||||
resolved := util.Ptr(false)
|
||||
targetAccount := suite.testAccounts["local_account_2"]
|
||||
|
||||
reports, link, err := suite.getReports(testAccount, testToken, testUser, http.StatusOK, "", resolved, "", targetAccount.ID, "", "", "", 20)
|
||||
|
|
|
@ -32,6 +32,7 @@ import (
|
|||
"github.com/superseriousbusiness/gotosocial/internal/api/client/instance"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
)
|
||||
|
||||
|
@ -231,7 +232,7 @@ func (suite *InstancePeersGetTestSuite) TestInstancePeersGetAllWithObfuscated()
|
|||
Domain: "omg.just.the.worst.org.ever",
|
||||
CreatedByAccountID: "01F8MH17FWEB39HZJ76B6VXSKF",
|
||||
PublicComment: "just absolutely the worst, wowza",
|
||||
Obfuscate: testrig.TrueBool(),
|
||||
Obfuscate: util.Ptr(true),
|
||||
})
|
||||
suite.NoError(err)
|
||||
|
||||
|
|
|
@ -32,6 +32,7 @@ import (
|
|||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
)
|
||||
|
||||
|
@ -197,7 +198,7 @@ func (suite *ReportsGetTestSuite) TestGetReports4() {
|
|||
testAccount := suite.testAccounts["local_account_2"]
|
||||
testToken := suite.testTokens["local_account_2"]
|
||||
testUser := suite.testUsers["local_account_2"]
|
||||
resolved := testrig.FalseBool()
|
||||
resolved := util.Ptr(false)
|
||||
|
||||
reports, link, err := suite.getReports(testAccount, testToken, testUser, http.StatusOK, resolved, "", "", "", "", 20)
|
||||
suite.NoError(err)
|
||||
|
@ -252,7 +253,7 @@ func (suite *ReportsGetTestSuite) TestGetReports5() {
|
|||
testAccount := suite.testAccounts["local_account_1"]
|
||||
testToken := suite.testTokens["local_account_1"]
|
||||
testUser := suite.testUsers["local_account_1"]
|
||||
resolved := testrig.TrueBool()
|
||||
resolved := util.Ptr(true)
|
||||
|
||||
reports, link, err := suite.getReports(testAccount, testToken, testUser, http.StatusOK, resolved, "", "", "", "", 20)
|
||||
suite.NoError(err)
|
||||
|
@ -323,7 +324,7 @@ func (suite *ReportsGetTestSuite) TestGetReports7() {
|
|||
testAccount := suite.testAccounts["local_account_2"]
|
||||
testToken := suite.testTokens["local_account_2"]
|
||||
testUser := suite.testUsers["local_account_2"]
|
||||
resolved := testrig.FalseBool()
|
||||
resolved := util.Ptr(false)
|
||||
|
||||
reports, link, err := suite.getReports(testAccount, testToken, testUser, http.StatusOK, resolved, "01F8MH5ZK5VRH73AKHQM6Y9VNX", "", "", "", 20)
|
||||
suite.NoError(err)
|
||||
|
|
|
@ -98,7 +98,7 @@ func (m *Module) StatusCreatePOSTHandler(c *gin.Context) {
|
|||
// }
|
||||
// form.Status += "\n\nsent from " + user + "'s iphone\n"
|
||||
|
||||
if err := validateCreateStatus(form); err != nil {
|
||||
if err := validateNormalizeCreateStatus(form); err != nil {
|
||||
apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
@ -112,7 +112,9 @@ func (m *Module) StatusCreatePOSTHandler(c *gin.Context) {
|
|||
c.JSON(http.StatusOK, apiStatus)
|
||||
}
|
||||
|
||||
func validateCreateStatus(form *apimodel.AdvancedStatusCreateForm) error {
|
||||
// validateNormalizeCreateStatus checks the form for disallowed combinations of attachments and overlength inputs.
|
||||
// Side effect: normalizes the post's language tag.
|
||||
func validateNormalizeCreateStatus(form *apimodel.AdvancedStatusCreateForm) error {
|
||||
hasStatus := form.Status != ""
|
||||
hasMedia := len(form.MediaIDs) != 0
|
||||
hasPoll := form.Poll != nil
|
||||
|
@ -162,9 +164,11 @@ func validateCreateStatus(form *apimodel.AdvancedStatusCreateForm) error {
|
|||
}
|
||||
|
||||
if form.Language != "" {
|
||||
if err := validate.Language(form.Language); err != nil {
|
||||
language, err := validate.Language(form.Language)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
form.Language = language
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
@ -391,6 +391,42 @@ func (suite *StatusCreateTestSuite) TestAttachNewMediaSuccess() {
|
|||
suite.Equal(statusResponse.ID, gtsAttachment.StatusID)
|
||||
}
|
||||
|
||||
// Post a new status with a language tag that is not in canonical format
|
||||
func (suite *StatusCreateTestSuite) TestPostNewStatusWithNoncanonicalLanguageTag() {
|
||||
t := suite.testTokens["local_account_1"]
|
||||
oauthToken := oauth.DBTokenToToken(t)
|
||||
|
||||
// setup
|
||||
recorder := httptest.NewRecorder()
|
||||
ctx, _ := testrig.CreateGinTestContext(recorder, nil)
|
||||
ctx.Set(oauth.SessionAuthorizedApplication, suite.testApplications["application_1"])
|
||||
ctx.Set(oauth.SessionAuthorizedToken, oauthToken)
|
||||
ctx.Set(oauth.SessionAuthorizedUser, suite.testUsers["local_account_1"])
|
||||
ctx.Set(oauth.SessionAuthorizedAccount, suite.testAccounts["local_account_1"])
|
||||
ctx.Request = httptest.NewRequest(http.MethodPost, fmt.Sprintf("http://localhost:8080/%s", statuses.BasePath), nil) // the endpoint we're hitting
|
||||
ctx.Request.Header.Set("accept", "application/json")
|
||||
ctx.Request.Form = url.Values{
|
||||
"status": {"English? what's English? i speak American"},
|
||||
"language": {"en-us"},
|
||||
}
|
||||
suite.statusModule.StatusCreatePOSTHandler(ctx)
|
||||
|
||||
suite.EqualValues(http.StatusOK, recorder.Code)
|
||||
|
||||
result := recorder.Result()
|
||||
defer result.Body.Close()
|
||||
b, err := ioutil.ReadAll(result.Body)
|
||||
suite.NoError(err)
|
||||
|
||||
statusReply := &apimodel.Status{}
|
||||
err = json.Unmarshal(b, statusReply)
|
||||
suite.NoError(err)
|
||||
|
||||
suite.Equal("<p>English? what's English? i speak American</p>", statusReply.Content)
|
||||
suite.NotNil(statusReply.Language)
|
||||
suite.Equal("en-US", *statusReply.Language)
|
||||
}
|
||||
|
||||
func TestStatusCreateTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(StatusCreateTestSuite))
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@ import (
|
|||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/id"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
)
|
||||
|
||||
|
@ -165,14 +166,14 @@ func (suite *StatusPinTestSuite) TestPinStatusTooManyPins() {
|
|||
PinnedAt: time.Now(),
|
||||
URL: "stub " + strconv.Itoa(i),
|
||||
URI: "stub " + strconv.Itoa(i),
|
||||
Local: testrig.TrueBool(),
|
||||
Local: util.Ptr(true),
|
||||
AccountID: testAccount.ID,
|
||||
AccountURI: testAccount.URI,
|
||||
Visibility: gtsmodel.VisibilityPublic,
|
||||
Federated: testrig.TrueBool(),
|
||||
Boostable: testrig.TrueBool(),
|
||||
Replyable: testrig.TrueBool(),
|
||||
Likeable: testrig.TrueBool(),
|
||||
Federated: util.Ptr(true),
|
||||
Boostable: util.Ptr(true),
|
||||
Replyable: util.Ptr(true),
|
||||
Likeable: util.Ptr(true),
|
||||
ActivityStreamsType: ap.ObjectNote,
|
||||
}
|
||||
if err := suite.db.PutStatus(ctx, status); err != nil {
|
||||
|
|
3
internal/cache/size.go
vendored
3
internal/cache/size.go
vendored
|
@ -157,12 +157,14 @@ func totalOfRatios() float64 {
|
|||
config.GetCacheAccountNoteMemRatio() +
|
||||
config.GetCacheBlockMemRatio() +
|
||||
config.GetCacheBlockIDsMemRatio() +
|
||||
config.GetCacheBoostOfIDsMemRatio() +
|
||||
config.GetCacheEmojiMemRatio() +
|
||||
config.GetCacheEmojiCategoryMemRatio() +
|
||||
config.GetCacheFollowMemRatio() +
|
||||
config.GetCacheFollowIDsMemRatio() +
|
||||
config.GetCacheFollowRequestMemRatio() +
|
||||
config.GetCacheFollowRequestIDsMemRatio() +
|
||||
config.GetCacheInReplyToIDsMemRatio() +
|
||||
config.GetCacheInstanceMemRatio() +
|
||||
config.GetCacheListMemRatio() +
|
||||
config.GetCacheListEntryMemRatio() +
|
||||
|
@ -173,6 +175,7 @@ func totalOfRatios() float64 {
|
|||
config.GetCacheReportMemRatio() +
|
||||
config.GetCacheStatusMemRatio() +
|
||||
config.GetCacheStatusFaveMemRatio() +
|
||||
config.GetCacheStatusFaveIDsMemRatio() +
|
||||
config.GetCacheTagMemRatio() +
|
||||
config.GetCacheTombstoneMemRatio() +
|
||||
config.GetCacheUserMemRatio() +
|
||||
|
|
|
@ -36,6 +36,7 @@ import (
|
|||
"github.com/superseriousbusiness/gotosocial/internal/log"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/state"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/uris"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
"github.com/uptrace/bun"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
@ -198,17 +199,15 @@ func (a *adminDB) NewSignup(ctx context.Context, newSignup gtsmodel.NewSignup) (
|
|||
user.Email = newSignup.Email
|
||||
}
|
||||
|
||||
trueBool := func() *bool { t := true; return &t }
|
||||
|
||||
if newSignup.Admin {
|
||||
// Make new user mod + admin.
|
||||
user.Moderator = trueBool()
|
||||
user.Admin = trueBool()
|
||||
user.Moderator = util.Ptr(true)
|
||||
user.Admin = util.Ptr(true)
|
||||
}
|
||||
|
||||
if newSignup.PreApproved {
|
||||
// Mark new user as approved.
|
||||
user.Approved = trueBool()
|
||||
user.Approved = util.Ptr(true)
|
||||
}
|
||||
|
||||
// Insert the user!
|
||||
|
|
|
@ -25,7 +25,7 @@ import (
|
|||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
)
|
||||
|
||||
type InstanceTestSuite struct {
|
||||
|
@ -103,7 +103,7 @@ func (suite *InstanceTestSuite) TestGetInstanceModeratorAddressesZorkAsModerator
|
|||
// Promote zork to moderator role.
|
||||
testUser := >smodel.User{}
|
||||
*testUser = *suite.testUsers["local_account_1"]
|
||||
testUser.Moderator = testrig.TrueBool()
|
||||
testUser.Moderator = util.Ptr(true)
|
||||
if err := suite.db.UpdateUser(context.Background(), testUser, "moderator"); err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
@ -117,8 +117,8 @@ func (suite *InstanceTestSuite) TestGetInstanceModeratorAddressesNoAdmin() {
|
|||
// Demote admin from admin + moderator roles.
|
||||
testUser := >smodel.User{}
|
||||
*testUser = *suite.testUsers["admin_account"]
|
||||
testUser.Admin = testrig.FalseBool()
|
||||
testUser.Moderator = testrig.FalseBool()
|
||||
testUser.Admin = util.Ptr(false)
|
||||
testUser.Moderator = util.Ptr(false)
|
||||
if err := suite.db.UpdateUser(context.Background(), testUser, "admin", "moderator"); err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ import (
|
|||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/id"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
)
|
||||
|
||||
func (suite *NotificationTestSuite) spamNotifs() {
|
||||
|
@ -70,7 +70,7 @@ func (suite *NotificationTestSuite) spamNotifs() {
|
|||
TargetAccountID: targetAccountID,
|
||||
OriginAccountID: originAccountID,
|
||||
StatusID: statusID,
|
||||
Read: testrig.FalseBool(),
|
||||
Read: util.Ptr(false),
|
||||
}
|
||||
|
||||
if err := suite.db.Put(context.Background(), notif); err != nil {
|
||||
|
|
|
@ -28,7 +28,7 @@ import (
|
|||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/id"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
)
|
||||
|
||||
type RelationshipTestSuite struct {
|
||||
|
@ -892,7 +892,7 @@ func (suite *RelationshipTestSuite) TestUpdateFollow() {
|
|||
follow := >smodel.Follow{}
|
||||
*follow = *suite.testFollows["local_account_1_admin_account"]
|
||||
|
||||
follow.Notify = testrig.TrueBool()
|
||||
follow.Notify = util.Ptr(true)
|
||||
if err := suite.db.UpdateFollow(ctx, follow, "notify"); err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ import (
|
|||
"github.com/stretchr/testify/suite"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
)
|
||||
|
||||
|
@ -88,7 +89,7 @@ func (suite *ReportTestSuite) TestPutReport() {
|
|||
TargetAccountID: "01F8MH5ZK5VRH73AKHQM6Y9VNX",
|
||||
Comment: "another report",
|
||||
StatusIDs: []string{"01FVW7JHQFSFK166WWKR8CBA6M"},
|
||||
Forwarded: testrig.TrueBool(),
|
||||
Forwarded: util.Ptr(true),
|
||||
}
|
||||
|
||||
err := suite.db.PutReport(ctx, report)
|
||||
|
|
|
@ -26,7 +26,7 @@ import (
|
|||
"github.com/superseriousbusiness/gotosocial/internal/ap"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/id"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
)
|
||||
|
||||
type TimelineTestSuite struct {
|
||||
|
@ -52,20 +52,20 @@ func getFutureStatus() *gtsmodel.Status {
|
|||
EmojiIDs: []string{},
|
||||
CreatedAt: theDistantFuture,
|
||||
UpdatedAt: theDistantFuture,
|
||||
Local: testrig.TrueBool(),
|
||||
Local: util.Ptr(true),
|
||||
AccountURI: "http://localhost:8080/users/admin",
|
||||
AccountID: "01F8MH17FWEB39HZJ76B6VXSKF",
|
||||
InReplyToID: "",
|
||||
BoostOfID: "",
|
||||
ContentWarning: "",
|
||||
Visibility: gtsmodel.VisibilityPublic,
|
||||
Sensitive: testrig.FalseBool(),
|
||||
Sensitive: util.Ptr(false),
|
||||
Language: "en",
|
||||
CreatedWithApplicationID: "01F8MGXQRHYF5QPMTMXP78QC2F",
|
||||
Federated: testrig.TrueBool(),
|
||||
Boostable: testrig.TrueBool(),
|
||||
Replyable: testrig.TrueBool(),
|
||||
Likeable: testrig.TrueBool(),
|
||||
Federated: util.Ptr(true),
|
||||
Boostable: util.Ptr(true),
|
||||
Replyable: util.Ptr(true),
|
||||
Likeable: util.Ptr(true),
|
||||
ActivityStreamsType: ap.ObjectNote,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ import (
|
|||
"github.com/stretchr/testify/suite"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/federation"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
)
|
||||
|
||||
|
@ -80,9 +81,9 @@ func (suite *FederatingActorTestSuite) TestSendRemoteFollower() {
|
|||
UpdatedAt: testrig.TimeMustParse("2022-06-02T12:22:21+02:00"),
|
||||
AccountID: testRemoteAccount.ID,
|
||||
TargetAccountID: testAccount.ID,
|
||||
ShowReblogs: testrig.TrueBool(),
|
||||
ShowReblogs: util.Ptr(true),
|
||||
URI: "http://fossbros-anonymous.io/users/foss_satan/follows/01G1TRWV4AYCDBX5HRWT2EVBCV",
|
||||
Notify: testrig.FalseBool(),
|
||||
Notify: util.Ptr(false),
|
||||
})
|
||||
suite.NoError(err)
|
||||
|
||||
|
|
|
@ -21,13 +21,13 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"codeberg.org/gruf/go-kv"
|
||||
"codeberg.org/gruf/go-logger/v2/level"
|
||||
"github.com/superseriousbusiness/activity/pub"
|
||||
"github.com/superseriousbusiness/activity/streams/vocab"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/ap"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/id"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/log"
|
||||
|
@ -47,14 +47,16 @@ import (
|
|||
// Under certain conditions and network activities, Create may be called
|
||||
// multiple times for the same ActivityStreams object.
|
||||
func (f *federatingDB) Create(ctx context.Context, asType vocab.Type) error {
|
||||
if log.Level() >= level.DEBUG {
|
||||
if log.Level() >= level.TRACE {
|
||||
i, err := marshalItem(asType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
l := log.WithContext(ctx).
|
||||
WithField("create", i)
|
||||
l.Trace("entering Create")
|
||||
|
||||
log.
|
||||
WithContext(ctx).
|
||||
WithField("create", i).
|
||||
Trace("entering Create")
|
||||
}
|
||||
|
||||
receivingAccount, requestingAccount, internal := extractFromCtx(ctx)
|
||||
|
@ -116,92 +118,125 @@ func (f *federatingDB) activityBlock(ctx context.Context, asType vocab.Type, rec
|
|||
CREATE HANDLERS
|
||||
*/
|
||||
|
||||
func (f *federatingDB) activityCreate(ctx context.Context, asType vocab.Type, receivingAccount *gtsmodel.Account, requestingAccount *gtsmodel.Account) error {
|
||||
// activityCreate handles asType Create by checking
|
||||
// the Object entries of the Create and calling other
|
||||
// handlers as appropriate.
|
||||
func (f *federatingDB) activityCreate(
|
||||
ctx context.Context,
|
||||
asType vocab.Type,
|
||||
receivingAccount *gtsmodel.Account,
|
||||
requestingAccount *gtsmodel.Account,
|
||||
) error {
|
||||
create, ok := asType.(vocab.ActivityStreamsCreate)
|
||||
if !ok {
|
||||
return errors.New("activityCreate: could not convert type to create")
|
||||
return gtserror.Newf("could not convert asType %T to ActivityStreamsCreate", asType)
|
||||
}
|
||||
|
||||
// create should have an object
|
||||
object := create.GetActivityStreamsObject()
|
||||
if object == nil {
|
||||
return errors.New("Create had no Object")
|
||||
// Create must have an Object.
|
||||
objectProp := create.GetActivityStreamsObject()
|
||||
if objectProp == nil {
|
||||
return gtserror.New("create had no Object")
|
||||
}
|
||||
|
||||
errs := []string{}
|
||||
// iterate through the object(s) to see what we're meant to be creating
|
||||
for objectIter := object.Begin(); objectIter != object.End(); objectIter = objectIter.Next() {
|
||||
asObjectType := objectIter.GetType()
|
||||
if asObjectType == nil {
|
||||
// currently we can't do anything with just a Create of something that's not an Object with a type
|
||||
// TODO: process a Create with an Object that's just a URI or something
|
||||
errs = append(errs, "object of Create was not a Type")
|
||||
// Iterate through the Object property and process FIRST provided statusable.
|
||||
// todo: https://github.com/superseriousbusiness/gotosocial/issues/1905
|
||||
for iter := objectProp.Begin(); iter != objectProp.End(); iter = iter.Next() {
|
||||
object := iter.GetType()
|
||||
if object == nil {
|
||||
// Can't do Create with Object that's just a URI.
|
||||
// Warn log this because it's an AP error.
|
||||
log.Warn(ctx, "object entry was not a type: %[1]T%[1]+v", iter)
|
||||
continue
|
||||
}
|
||||
|
||||
// we have a type -- what is it?
|
||||
asObjectTypeName := asObjectType.GetTypeName()
|
||||
switch asObjectTypeName {
|
||||
case ap.ObjectNote:
|
||||
// CREATE A NOTE
|
||||
if err := f.createNote(ctx, objectIter.GetActivityStreamsNote(), receivingAccount, requestingAccount); err != nil {
|
||||
errs = append(errs, err.Error())
|
||||
}
|
||||
default:
|
||||
errs = append(errs, fmt.Sprintf("received an object on a Create that we couldn't handle: %s", asObjectType.GetTypeName()))
|
||||
// Ensure given object type is a statusable.
|
||||
statusable, ok := object.(ap.Statusable)
|
||||
if !ok {
|
||||
// Can't (currently) Create anything other than a Statusable. ([1] is a format arg index)
|
||||
log.Debugf(ctx, "object entry type (currently) unsupported: %[1]T%[1]+v", object)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if len(errs) != 0 {
|
||||
return fmt.Errorf("activityCreate: one or more errors while processing activity: %s", strings.Join(errs, "; "))
|
||||
// Handle creation of statusable.
|
||||
return f.createStatusable(ctx,
|
||||
statusable,
|
||||
receivingAccount,
|
||||
requestingAccount,
|
||||
)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// createNote handles a Create activity with a Note type.
|
||||
func (f *federatingDB) createNote(ctx context.Context, note vocab.ActivityStreamsNote, receivingAccount *gtsmodel.Account, requestingAccount *gtsmodel.Account) error {
|
||||
l := log.WithContext(ctx).
|
||||
WithFields(kv.Fields{
|
||||
{"receivingAccount", receivingAccount.URI},
|
||||
{"requestingAccount", requestingAccount.URI},
|
||||
}...)
|
||||
// createStatusable handles a Create activity for a Statusable.
|
||||
// This function won't insert anything in the database yet,
|
||||
// but will pass the Statusable (if appropriate) through to
|
||||
// the processor for further asynchronous processing.
|
||||
func (f *federatingDB) createStatusable(
|
||||
ctx context.Context,
|
||||
statusable ap.Statusable,
|
||||
receivingAccount *gtsmodel.Account,
|
||||
requestingAccount *gtsmodel.Account,
|
||||
) error {
|
||||
// Statusable must have an attributedTo.
|
||||
attrToProp := statusable.GetActivityStreamsAttributedTo()
|
||||
if attrToProp == nil {
|
||||
return gtserror.Newf("statusable had no attributedTo")
|
||||
}
|
||||
|
||||
// Check if we have a forward.
|
||||
// In other words, was the note posted to our inbox by at least one actor who actually created the note, or are they just forwarding it?
|
||||
// Statusable must have an ID.
|
||||
idProp := statusable.GetJSONLDId()
|
||||
if idProp == nil || !idProp.IsIRI() {
|
||||
return gtserror.Newf("statusable had no id, or id was not a URI")
|
||||
}
|
||||
|
||||
statusableURI := idProp.GetIRI()
|
||||
|
||||
// Check if we have a forward. In other words, was the
|
||||
// statusable posted to our inbox by at least one actor
|
||||
// who actually created it, or are they forwarding it?
|
||||
forward := true
|
||||
|
||||
// note should have an attributedTo
|
||||
noteAttributedTo := note.GetActivityStreamsAttributedTo()
|
||||
if noteAttributedTo == nil {
|
||||
return errors.New("createNote: note had no attributedTo")
|
||||
}
|
||||
|
||||
// compare the attributedTo(s) with the actor who posted this to our inbox
|
||||
for attributedToIter := noteAttributedTo.Begin(); attributedToIter != noteAttributedTo.End(); attributedToIter = attributedToIter.Next() {
|
||||
if !attributedToIter.IsIRI() {
|
||||
continue
|
||||
for iter := attrToProp.Begin(); iter != attrToProp.End(); iter = iter.Next() {
|
||||
actorURI, err := pub.ToId(iter)
|
||||
if err != nil {
|
||||
return gtserror.Newf("error extracting id from attributedTo entry: %w", err)
|
||||
}
|
||||
iri := attributedToIter.GetIRI()
|
||||
if requestingAccount.URI == iri.String() {
|
||||
// at least one creator of the note, and the actor who posted the note to our inbox, are the same, so it's not a forward
|
||||
|
||||
if requestingAccount.URI == actorURI.String() {
|
||||
// The actor who posted this statusable to our inbox is
|
||||
// (one of) its creator(s), so this is not a forward.
|
||||
forward = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// If we do have a forward, we should ignore the content for now and just dereference based on the URL/ID of the note instead, to get the note straight from the horse's mouth
|
||||
// Check if we already have a status entry
|
||||
// for this statusable, based on the ID/URI.
|
||||
statusableURIStr := statusableURI.String()
|
||||
status, err := f.state.DB.GetStatusByURI(ctx, statusableURIStr)
|
||||
if err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||
return gtserror.Newf("db error checking existence of status %s: %w", statusableURIStr, err)
|
||||
}
|
||||
|
||||
if status != nil {
|
||||
// We already had this status in the db, no need for further action.
|
||||
log.Trace(ctx, "status already exists: %s", statusableURIStr)
|
||||
return nil
|
||||
}
|
||||
|
||||
// If we do have a forward, we should ignore the content
|
||||
// and instead deref based on the URI of the statusable.
|
||||
//
|
||||
// In other words, don't automatically trust whoever sent
|
||||
// this status to us, but fetch the authentic article from
|
||||
// the server it originated from.
|
||||
if forward {
|
||||
l.Trace("note is a forward")
|
||||
id := note.GetJSONLDId()
|
||||
if !id.IsIRI() {
|
||||
// if the note id isn't an IRI, there's nothing we can do here
|
||||
return nil
|
||||
}
|
||||
// pass the note iri into the processor and have it do the dereferencing instead of doing it here
|
||||
// Pass the statusable URI (APIri) into the processor worker
|
||||
// and do the rest of the processing asynchronously.
|
||||
f.state.Workers.EnqueueFederator(ctx, messages.FromFederator{
|
||||
APObjectType: ap.ObjectNote,
|
||||
APActivityType: ap.ActivityCreate,
|
||||
APIri: id.GetIRI(),
|
||||
APIri: statusableURI,
|
||||
APObjectModel: nil,
|
||||
GTSModel: nil,
|
||||
ReceivingAccount: receivingAccount,
|
||||
|
@ -209,34 +244,58 @@ func (f *federatingDB) createNote(ctx context.Context, note vocab.ActivityStream
|
|||
return nil
|
||||
}
|
||||
|
||||
// if we reach this point, we know it's not a forwarded status, so proceed with processing it as normal
|
||||
|
||||
status, err := f.typeConverter.ASStatusToStatus(ctx, note)
|
||||
// This is a non-forwarded status we can trust the requester on,
|
||||
// convert this provided statusable data to a useable gtsmodel status.
|
||||
status, err = f.typeConverter.ASStatusToStatus(ctx, statusable)
|
||||
if err != nil {
|
||||
return fmt.Errorf("createNote: error converting note to status: %s", err)
|
||||
return gtserror.Newf("error converting statusable to status: %w", err)
|
||||
}
|
||||
|
||||
// id the status based on the time it was created
|
||||
statusID, err := id.NewULIDFromTime(status.CreatedAt)
|
||||
// Check whether we should accept this new status.
|
||||
accept, err := f.shouldAcceptStatusable(ctx,
|
||||
receivingAccount,
|
||||
requestingAccount,
|
||||
status,
|
||||
)
|
||||
if err != nil {
|
||||
return gtserror.Newf("error checking status acceptibility: %w", err)
|
||||
}
|
||||
|
||||
if !accept {
|
||||
// This is a status sent with no relation to receiver, i.e.
|
||||
// - receiving account does not follow requesting account
|
||||
// - received status does not mention receiving account
|
||||
//
|
||||
// We just pretend that all is fine (dog with cuppa, flames everywhere)
|
||||
log.Trace(ctx, "status failed acceptability check")
|
||||
return nil
|
||||
}
|
||||
|
||||
// ID the new status based on the time it was created.
|
||||
status.ID, err = id.NewULIDFromTime(status.CreatedAt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
status.ID = statusID
|
||||
|
||||
// Put this newly parsed status in the database.
|
||||
if err := f.state.DB.PutStatus(ctx, status); err != nil {
|
||||
if errors.Is(err, db.ErrAlreadyExists) {
|
||||
// the status already exists in the database, which means we've already handled everything else,
|
||||
// so we can just return nil here and be done with it.
|
||||
// The status already exists in the database, which
|
||||
// means we've already processed it and some race
|
||||
// condition means we didn't catch it yet. We can
|
||||
// just return nil here and be done with it.
|
||||
return nil
|
||||
}
|
||||
// an actual error has happened
|
||||
return fmt.Errorf("createNote: database error inserting status: %s", err)
|
||||
return gtserror.Newf("db error inserting status: %w", err)
|
||||
}
|
||||
|
||||
// Do the rest of the processing asynchronously. The processor
|
||||
// will handle inserting/updating + further dereferencing the status.
|
||||
f.state.Workers.EnqueueFederator(ctx, messages.FromFederator{
|
||||
APObjectType: ap.ObjectNote,
|
||||
APActivityType: ap.ActivityCreate,
|
||||
APObjectModel: note,
|
||||
APIri: nil,
|
||||
APObjectModel: statusable,
|
||||
GTSModel: status,
|
||||
ReceivingAccount: receivingAccount,
|
||||
})
|
||||
|
@ -244,6 +303,26 @@ func (f *federatingDB) createNote(ctx context.Context, note vocab.ActivityStream
|
|||
return nil
|
||||
}
|
||||
|
||||
func (f *federatingDB) shouldAcceptStatusable(ctx context.Context, receiver *gtsmodel.Account, requester *gtsmodel.Account, status *gtsmodel.Status) (bool, error) {
|
||||
// Check whether status mentions the receiver,
|
||||
// this is the quickest check so perform it first.
|
||||
for _, mention := range status.Mentions {
|
||||
if mention.TargetAccountURI == receiver.URI {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Check whether receiving account follows the requesting account.
|
||||
follows, err := f.state.DB.IsFollowing(ctx, receiver.ID, requester.ID)
|
||||
if err != nil {
|
||||
return false, gtserror.Newf("error checking follow status: %w", err)
|
||||
}
|
||||
|
||||
// Status will only be acceptable
|
||||
// if receiver follows requester.
|
||||
return follows, nil
|
||||
}
|
||||
|
||||
/*
|
||||
FOLLOW HANDLERS
|
||||
*/
|
||||
|
|
|
@ -54,7 +54,7 @@ func (suite *CreateTestSuite) TestCreateNote() {
|
|||
|
||||
// status should have some expected values
|
||||
suite.Equal(requestingAccount.ID, status.AccountID)
|
||||
suite.Equal("hey zork here's a new private note for you", status.Content)
|
||||
suite.Equal("@the_mighty_zork@localhost:8080 hey zork here's a new private note for you", status.Content)
|
||||
|
||||
// status should be in the database
|
||||
_, err = suite.db.GetStatusByID(context.Background(), status.ID)
|
||||
|
|
|
@ -32,55 +32,55 @@ import (
|
|||
|
||||
// Account represents either a local or a remote fediverse account, gotosocial or otherwise (mastodon, pleroma, etc).
|
||||
type Account struct {
|
||||
ID string `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
|
||||
CreatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created.
|
||||
UpdatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item was last updated.
|
||||
FetchedAt time.Time `validate:"required_with=Domain" bun:"type:timestamptz,nullzero"` // when was item (remote) last fetched.
|
||||
Username string `validate:"required" bun:",nullzero,notnull,unique:usernamedomain"` // Username of the account, should just be a string of [a-zA-Z0-9_]. Can be added to domain to create the full username in the form ``[username]@[domain]`` eg., ``user_96@example.org``. Username and domain should be unique *with* each other
|
||||
Domain string `validate:"omitempty,fqdn" bun:",nullzero,unique:usernamedomain"` // Domain of the account, will be null if this is a local account, otherwise something like ``example.org``. Should be unique with username.
|
||||
AvatarMediaAttachmentID string `validate:"omitempty,ulid" bun:"type:CHAR(26),nullzero"` // Database ID of the media attachment, if present
|
||||
AvatarMediaAttachment *MediaAttachment `validate:"-" bun:"rel:belongs-to"` // MediaAttachment corresponding to avatarMediaAttachmentID
|
||||
AvatarRemoteURL string `validate:"omitempty,url" bun:",nullzero"` // For a non-local account, where can the header be fetched?
|
||||
HeaderMediaAttachmentID string `validate:"omitempty,ulid" bun:"type:CHAR(26),nullzero"` // Database ID of the media attachment, if present
|
||||
HeaderMediaAttachment *MediaAttachment `validate:"-" bun:"rel:belongs-to"` // MediaAttachment corresponding to headerMediaAttachmentID
|
||||
HeaderRemoteURL string `validate:"omitempty,url" bun:",nullzero"` // For a non-local account, where can the header be fetched?
|
||||
DisplayName string `validate:"-" bun:""` // DisplayName for this account. Can be empty, then just the Username will be used for display purposes.
|
||||
EmojiIDs []string `validate:"dive,ulid" bun:"emojis,array"` // Database IDs of any emojis used in this account's bio, display name, etc
|
||||
Emojis []*Emoji `validate:"-" bun:"attached_emojis,m2m:account_to_emojis"` // Emojis corresponding to emojiIDs. https://bun.uptrace.dev/guide/relations.html#many-to-many-relation
|
||||
Fields []*Field `validate:"-"` // A slice of of fields that this account has added to their profile.
|
||||
FieldsRaw []*Field `validate:"-"` // The raw (unparsed) content of fields that this account has added to their profile, without conversion to HTML, only available when requester = target
|
||||
Note string `validate:"-" bun:""` // A note that this account has on their profile (ie., the account's bio/description of themselves)
|
||||
NoteRaw string `validate:"-" bun:""` // The raw contents of .Note without conversion to HTML, only available when requester = target
|
||||
Memorial *bool `validate:"-" bun:",default:false"` // Is this a memorial account, ie., has the user passed away?
|
||||
AlsoKnownAs string `validate:"omitempty,ulid" bun:"type:CHAR(26),nullzero"` // This account is associated with x account id (TODO: migrate to be AlsoKnownAsID)
|
||||
MovedToAccountID string `validate:"omitempty,ulid" bun:"type:CHAR(26),nullzero"` // This account has moved this account id in the database
|
||||
Bot *bool `validate:"-" bun:",default:false"` // Does this account identify itself as a bot?
|
||||
Reason string `validate:"-" bun:""` // What reason was given for signing up when this account was created?
|
||||
Locked *bool `validate:"-" bun:",default:true"` // Does this account need an approval for new followers?
|
||||
Discoverable *bool `validate:"-" bun:",default:false"` // Should this account be shown in the instance's profile directory?
|
||||
Privacy Visibility `validate:"required_without=Domain,omitempty,oneof=public unlocked followers_only mutuals_only direct" bun:",nullzero"` // Default post privacy for this account
|
||||
Sensitive *bool `validate:"-" bun:",default:false"` // Set posts from this account to sensitive by default?
|
||||
Language string `validate:"omitempty,bcp47_language_tag" bun:",nullzero,notnull,default:'en'"` // What language does this account post in?
|
||||
StatusContentType string `validate:"required_without=Domain,omitempty,oneof=text/plain text/markdown" bun:",nullzero"` // What is the default format for statuses posted by this account (only for local accounts).
|
||||
CustomCSS string `validate:"-" bun:",nullzero"` // Custom CSS that should be displayed for this Account's profile and statuses.
|
||||
URI string `validate:"required,url" bun:",nullzero,notnull,unique"` // ActivityPub URI for this account.
|
||||
URL string `validate:"required_without=Domain,omitempty,url" bun:",nullzero,unique"` // Web URL for this account's profile
|
||||
InboxURI string `validate:"required_without=Domain,omitempty,url" bun:",nullzero,unique"` // Address of this account's ActivityPub inbox, for sending activity to
|
||||
SharedInboxURI *string `validate:"-" bun:""` // Address of this account's ActivityPub sharedInbox. Gotcha warning: this is a string pointer because it has three possible states: 1. We don't know yet if the account has a shared inbox -- null. 2. We know it doesn't have a shared inbox -- empty string. 3. We know it does have a shared inbox -- url string.
|
||||
OutboxURI string `validate:"required_without=Domain,omitempty,url" bun:",nullzero,unique"` // Address of this account's activitypub outbox
|
||||
FollowingURI string `validate:"required_without=Domain,omitempty,url" bun:",nullzero,unique"` // URI for getting the following list of this account
|
||||
FollowersURI string `validate:"required_without=Domain,omitempty,url" bun:",nullzero,unique"` // URI for getting the followers list of this account
|
||||
FeaturedCollectionURI string `validate:"required_without=Domain,omitempty,url" bun:",nullzero,unique"` // URL for getting the featured collection list of this account
|
||||
ActorType string `validate:"oneof=Application Group Organization Person Service" bun:",nullzero,notnull"` // What type of activitypub actor is this account?
|
||||
PrivateKey *rsa.PrivateKey `validate:"required_without=Domain" bun:""` // Privatekey for validating activitypub requests, will only be defined for local accounts
|
||||
PublicKey *rsa.PublicKey `validate:"required" bun:",notnull"` // Publickey for encoding activitypub requests, will be defined for both local and remote accounts
|
||||
PublicKeyURI string `validate:"required,url" bun:",nullzero,notnull,unique"` // Web-reachable location of this account's public key
|
||||
SensitizedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero"` // When was this account set to have all its media shown as sensitive?
|
||||
SilencedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero"` // When was this account silenced (eg., statuses only visible to followers, not public)?
|
||||
SuspendedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero"` // When was this account suspended (eg., don't allow it to log in/post, don't accept media/posts from this account)
|
||||
HideCollections *bool `validate:"-" bun:",default:false"` // Hide this account's collections
|
||||
SuspensionOrigin string `validate:"omitempty,ulid" bun:"type:CHAR(26),nullzero"` // id of the database entry that caused this account to become suspended -- can be an account ID or a domain block ID
|
||||
EnableRSS *bool `validate:"-" bun:",default:false"` // enable RSS feed subscription for this account's public posts at [URL]/feed
|
||||
ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
|
||||
CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created.
|
||||
UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item was last updated.
|
||||
FetchedAt time.Time `bun:"type:timestamptz,nullzero"` // when was item (remote) last fetched.
|
||||
Username string `bun:",nullzero,notnull,unique:usernamedomain"` // Username of the account, should just be a string of [a-zA-Z0-9_]. Can be added to domain to create the full username in the form ``[username]@[domain]`` eg., ``user_96@example.org``. Username and domain should be unique *with* each other
|
||||
Domain string `bun:",nullzero,unique:usernamedomain"` // Domain of the account, will be null if this is a local account, otherwise something like ``example.org``. Should be unique with username.
|
||||
AvatarMediaAttachmentID string `bun:"type:CHAR(26),nullzero"` // Database ID of the media attachment, if present
|
||||
AvatarMediaAttachment *MediaAttachment `bun:"rel:belongs-to"` // MediaAttachment corresponding to avatarMediaAttachmentID
|
||||
AvatarRemoteURL string `bun:",nullzero"` // For a non-local account, where can the header be fetched?
|
||||
HeaderMediaAttachmentID string `bun:"type:CHAR(26),nullzero"` // Database ID of the media attachment, if present
|
||||
HeaderMediaAttachment *MediaAttachment `bun:"rel:belongs-to"` // MediaAttachment corresponding to headerMediaAttachmentID
|
||||
HeaderRemoteURL string `bun:",nullzero"` // For a non-local account, where can the header be fetched?
|
||||
DisplayName string `bun:""` // DisplayName for this account. Can be empty, then just the Username will be used for display purposes.
|
||||
EmojiIDs []string `bun:"emojis,array"` // Database IDs of any emojis used in this account's bio, display name, etc
|
||||
Emojis []*Emoji `bun:"attached_emojis,m2m:account_to_emojis"` // Emojis corresponding to emojiIDs. https://bun.uptrace.dev/guide/relations.html#many-to-many-relation
|
||||
Fields []*Field // A slice of of fields that this account has added to their profile.
|
||||
FieldsRaw []*Field // The raw (unparsed) content of fields that this account has added to their profile, without conversion to HTML, only available when requester = target
|
||||
Note string `bun:""` // A note that this account has on their profile (ie., the account's bio/description of themselves)
|
||||
NoteRaw string `bun:""` // The raw contents of .Note without conversion to HTML, only available when requester = target
|
||||
Memorial *bool `bun:",default:false"` // Is this a memorial account, ie., has the user passed away?
|
||||
AlsoKnownAs string `bun:"type:CHAR(26),nullzero"` // This account is associated with x account id (TODO: migrate to be AlsoKnownAsID)
|
||||
MovedToAccountID string `bun:"type:CHAR(26),nullzero"` // This account has moved this account id in the database
|
||||
Bot *bool `bun:",default:false"` // Does this account identify itself as a bot?
|
||||
Reason string `bun:""` // What reason was given for signing up when this account was created?
|
||||
Locked *bool `bun:",default:true"` // Does this account need an approval for new followers?
|
||||
Discoverable *bool `bun:",default:false"` // Should this account be shown in the instance's profile directory?
|
||||
Privacy Visibility `bun:",nullzero"` // Default post privacy for this account
|
||||
Sensitive *bool `bun:",default:false"` // Set posts from this account to sensitive by default?
|
||||
Language string `bun:",nullzero,notnull,default:'en'"` // What language does this account post in?
|
||||
StatusContentType string `bun:",nullzero"` // What is the default format for statuses posted by this account (only for local accounts).
|
||||
CustomCSS string `bun:",nullzero"` // Custom CSS that should be displayed for this Account's profile and statuses.
|
||||
URI string `bun:",nullzero,notnull,unique"` // ActivityPub URI for this account.
|
||||
URL string `bun:",nullzero,unique"` // Web URL for this account's profile
|
||||
InboxURI string `bun:",nullzero,unique"` // Address of this account's ActivityPub inbox, for sending activity to
|
||||
SharedInboxURI *string `bun:""` // Address of this account's ActivityPub sharedInbox. Gotcha warning: this is a string pointer because it has three possible states: 1. We don't know yet if the account has a shared inbox -- null. 2. We know it doesn't have a shared inbox -- empty string. 3. We know it does have a shared inbox -- url string.
|
||||
OutboxURI string `bun:",nullzero,unique"` // Address of this account's activitypub outbox
|
||||
FollowingURI string `bun:",nullzero,unique"` // URI for getting the following list of this account
|
||||
FollowersURI string `bun:",nullzero,unique"` // URI for getting the followers list of this account
|
||||
FeaturedCollectionURI string `bun:",nullzero,unique"` // URL for getting the featured collection list of this account
|
||||
ActorType string `bun:",nullzero,notnull"` // What type of activitypub actor is this account?
|
||||
PrivateKey *rsa.PrivateKey `bun:""` // Privatekey for validating activitypub requests, will only be defined for local accounts
|
||||
PublicKey *rsa.PublicKey `bun:",notnull"` // Publickey for encoding activitypub requests, will be defined for both local and remote accounts
|
||||
PublicKeyURI string `bun:",nullzero,notnull,unique"` // Web-reachable location of this account's public key
|
||||
SensitizedAt time.Time `bun:"type:timestamptz,nullzero"` // When was this account set to have all its media shown as sensitive?
|
||||
SilencedAt time.Time `bun:"type:timestamptz,nullzero"` // When was this account silenced (eg., statuses only visible to followers, not public)?
|
||||
SuspendedAt time.Time `bun:"type:timestamptz,nullzero"` // When was this account suspended (eg., don't allow it to log in/post, don't accept media/posts from this account)
|
||||
HideCollections *bool `bun:",default:false"` // Hide this account's collections
|
||||
SuspensionOrigin string `bun:"type:CHAR(26),nullzero"` // id of the database entry that caused this account to become suspended -- can be an account ID or a domain block ID
|
||||
EnableRSS *bool `bun:",default:false"` // enable RSS feed subscription for this account's public posts at [URL]/feed
|
||||
}
|
||||
|
||||
// IsLocal returns whether account is a local user account.
|
||||
|
@ -131,19 +131,19 @@ func (a *Account) EmojisPopulated() bool {
|
|||
|
||||
// AccountToEmoji is an intermediate struct to facilitate the many2many relationship between an account and one or more emojis.
|
||||
type AccountToEmoji struct {
|
||||
AccountID string `validate:"ulid,required" bun:"type:CHAR(26),unique:accountemoji,nullzero,notnull"`
|
||||
Account *Account `validate:"-" bun:"rel:belongs-to"`
|
||||
EmojiID string `validate:"ulid,required" bun:"type:CHAR(26),unique:accountemoji,nullzero,notnull"`
|
||||
Emoji *Emoji `validate:"-" bun:"rel:belongs-to"`
|
||||
AccountID string `bun:"type:CHAR(26),unique:accountemoji,nullzero,notnull"`
|
||||
Account *Account `bun:"rel:belongs-to"`
|
||||
EmojiID string `bun:"type:CHAR(26),unique:accountemoji,nullzero,notnull"`
|
||||
Emoji *Emoji `bun:"rel:belongs-to"`
|
||||
}
|
||||
|
||||
// Field represents a key value field on an account, for things like pronouns, website, etc.
|
||||
// VerifiedAt is optional, to be used only if Value is a URL to a webpage that contains the
|
||||
// username of the user.
|
||||
type Field struct {
|
||||
Name string `validate:"required"` // Name of this field.
|
||||
Value string `validate:"required"` // Value of this field.
|
||||
VerifiedAt time.Time `validate:"-" bun:",nullzero"` // This field was verified at (optional).
|
||||
Name string // Name of this field.
|
||||
Value string // Value of this field.
|
||||
VerifiedAt time.Time `bun:",nullzero"` // This field was verified at (optional).
|
||||
}
|
||||
|
||||
// Relationship describes a requester's relationship with another account.
|
||||
|
|
|
@ -21,12 +21,12 @@ import "time"
|
|||
|
||||
// AccountNote stores a private note from a local account related to any account.
|
||||
type AccountNote struct {
|
||||
ID string `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
|
||||
CreatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
|
||||
UpdatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
|
||||
AccountID string `validate:"required,ulid" bun:"type:CHAR(26),unique:account_notes_account_id_target_account_id_uniq,notnull,nullzero"` // ID of the local account that created the note
|
||||
Account *Account `validate:"-" bun:"rel:belongs-to"` // Account corresponding to accountID
|
||||
TargetAccountID string `validate:"required,ulid" bun:"type:CHAR(26),unique:account_notes_account_id_target_account_id_uniq,notnull,nullzero"` // Who is the target of this note?
|
||||
TargetAccount *Account `validate:"-" bun:"rel:belongs-to"` // Account corresponding to targetAccountID
|
||||
Comment string `validate:"-" bun:""` // The text of the note.
|
||||
ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
|
||||
CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
|
||||
UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
|
||||
AccountID string `bun:"type:CHAR(26),unique:account_notes_account_id_target_account_id_uniq,notnull,nullzero"` // ID of the local account that created the note
|
||||
Account *Account `bun:"rel:belongs-to"` // Account corresponding to accountID
|
||||
TargetAccountID string `bun:"type:CHAR(26),unique:account_notes_account_id_target_account_id_uniq,notnull,nullzero"` // Who is the target of this note?
|
||||
TargetAccount *Account `bun:"rel:belongs-to"` // Account corresponding to targetAccountID
|
||||
Comment string `bun:""` // The text of the note.
|
||||
}
|
||||
|
|
|
@ -24,17 +24,17 @@ import (
|
|||
|
||||
// AdminAccountAction models an action taken by an instance administrator on an account.
|
||||
type AdminAccountAction struct {
|
||||
ID string `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
|
||||
CreatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
|
||||
UpdatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
|
||||
AccountID string `validate:"required,ulid" bun:"type:CHAR(26),notnull,nullzero"` // Who performed this admin action.
|
||||
Account *Account `validate:"-" bun:"rel:has-one"` // Account corresponding to accountID
|
||||
TargetAccountID string `validate:"required,ulid" bun:"type:CHAR(26),notnull,nullzero"` // Who is the target of this action
|
||||
TargetAccount *Account `validate:"-" bun:"rel:has-one"` // Account corresponding to targetAccountID
|
||||
Text string `validate:"-" bun:""` // text explaining why this action was taken
|
||||
Type AdminActionType `validate:"oneof=disable silence suspend" bun:",nullzero,notnull"` // type of action that was taken
|
||||
SendEmail bool `validate:"-" bun:""` // should an email be sent to the account owner to explain what happened
|
||||
ReportID string `validate:",omitempty,ulid" bun:"type:CHAR(26),nullzero"` // id of a report connected to this action, if it exists
|
||||
ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
|
||||
CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
|
||||
UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
|
||||
AccountID string `bun:"type:CHAR(26),notnull,nullzero"` // Who performed this admin action.
|
||||
Account *Account `bun:"rel:has-one"` // Account corresponding to accountID
|
||||
TargetAccountID string `bun:"type:CHAR(26),notnull,nullzero"` // Who is the target of this action
|
||||
TargetAccount *Account `bun:"rel:has-one"` // Account corresponding to targetAccountID
|
||||
Text string `bun:""` // text explaining why this action was taken
|
||||
Type AdminActionType `bun:",nullzero,notnull"` // type of action that was taken
|
||||
SendEmail bool `bun:""` // should an email be sent to the account owner to explain what happened
|
||||
ReportID string `bun:"type:CHAR(26),nullzero"` // id of a report connected to this action, if it exists
|
||||
}
|
||||
|
||||
// AdminActionType describes a type of action taken on an entity by an admin
|
||||
|
|
|
@ -22,13 +22,13 @@ import "time"
|
|||
// Application represents an application that can perform actions on behalf of a user.
|
||||
// It is used to authorize tokens etc, and is associated with an oauth client id in the database.
|
||||
type Application struct {
|
||||
ID string `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
|
||||
CreatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
|
||||
UpdatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
|
||||
Name string `validate:"required" bun:",notnull"` // name of the application given when it was created (eg., 'tusky')
|
||||
Website string `validate:"omitempty,url" bun:",nullzero"` // website for the application given when it was created (eg., 'https://tusky.app')
|
||||
RedirectURI string `validate:"required,uri" bun:",nullzero,notnull"` // redirect uri requested by the application for oauth2 flow
|
||||
ClientID string `validate:"required,ulid" bun:"type:CHAR(26),nullzero,notnull"` // id of the associated oauth client entity in the db
|
||||
ClientSecret string `validate:"required,uuid" bun:",nullzero,notnull"` // secret of the associated oauth client entity in the db
|
||||
Scopes string `validate:"required" bun:",notnull"` // scopes requested when this app was created
|
||||
ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
|
||||
CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
|
||||
UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
|
||||
Name string `bun:",notnull"` // name of the application given when it was created (eg., 'tusky')
|
||||
Website string `bun:",nullzero"` // website for the application given when it was created (eg., 'https://tusky.app')
|
||||
RedirectURI string `bun:",nullzero,notnull"` // redirect uri requested by the application for oauth2 flow
|
||||
ClientID string `bun:"type:CHAR(26),nullzero,notnull"` // id of the associated oauth client entity in the db
|
||||
ClientSecret string `bun:",nullzero,notnull"` // secret of the associated oauth client entity in the db
|
||||
Scopes string `bun:",notnull"` // scopes requested when this app was created
|
||||
}
|
||||
|
|
|
@ -21,12 +21,12 @@ import "time"
|
|||
|
||||
// Block refers to the blocking of one account by another.
|
||||
type Block struct {
|
||||
ID string `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
|
||||
CreatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
|
||||
UpdatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
|
||||
URI string `validate:"required,url" bun:",notnull,nullzero,unique"` // ActivityPub uri of this block.
|
||||
AccountID string `validate:"required,ulid" bun:"type:CHAR(26),unique:blocksrctarget,notnull,nullzero"` // Who does this block originate from?
|
||||
Account *Account `validate:"-" bun:"rel:belongs-to"` // Account corresponding to accountID
|
||||
TargetAccountID string `validate:"required,ulid" bun:"type:CHAR(26),unique:blocksrctarget,notnull,nullzero"` // Who is the target of this block ?
|
||||
TargetAccount *Account `validate:"-" bun:"rel:belongs-to"` // Account corresponding to targetAccountID
|
||||
ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
|
||||
CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
|
||||
UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
|
||||
URI string `bun:",notnull,nullzero,unique"` // ActivityPub uri of this block.
|
||||
AccountID string `bun:"type:CHAR(26),unique:blocksrctarget,notnull,nullzero"` // Who does this block originate from?
|
||||
Account *Account `bun:"rel:belongs-to"` // Account corresponding to accountID
|
||||
TargetAccountID string `bun:"type:CHAR(26),unique:blocksrctarget,notnull,nullzero"` // Who is the target of this block ?
|
||||
TargetAccount *Account `bun:"rel:belongs-to"` // Account corresponding to targetAccountID
|
||||
}
|
||||
|
|
|
@ -21,10 +21,10 @@ import "time"
|
|||
|
||||
// Client is a wrapper for OAuth client details.
|
||||
type Client struct {
|
||||
ID string `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
|
||||
CreatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
|
||||
UpdatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
|
||||
Secret string `validate:"required,uuid" bun:",nullzero,notnull"` // secret generated when client was created
|
||||
Domain string `validate:"required,uri" bun:",nullzero,notnull"` // domain requested for client
|
||||
UserID string `validate:"omitempty,ulid" bun:"type:CHAR(26),nullzero"` // id of the user that this client acts on behalf of
|
||||
ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
|
||||
CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
|
||||
UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
|
||||
Secret string `bun:",nullzero,notnull"` // secret generated when client was created
|
||||
Domain string `bun:",nullzero,notnull"` // domain requested for client
|
||||
UserID string `bun:"type:CHAR(26),nullzero"` // id of the user that this client acts on behalf of
|
||||
}
|
||||
|
|
|
@ -21,14 +21,14 @@ import "time"
|
|||
|
||||
// DomainBlock represents a federation block against a particular domain
|
||||
type DomainBlock struct {
|
||||
ID string `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
|
||||
CreatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
|
||||
UpdatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
|
||||
Domain string `validate:"required,fqdn" bun:",nullzero,notnull"` // domain to block. Eg. 'whatever.com'
|
||||
CreatedByAccountID string `validate:"required,ulid" bun:"type:CHAR(26),nullzero,notnull"` // Account ID of the creator of this block
|
||||
CreatedByAccount *Account `validate:"-" bun:"rel:belongs-to"` // Account corresponding to createdByAccountID
|
||||
PrivateComment string `validate:"-" bun:""` // Private comment on this block, viewable to admins
|
||||
PublicComment string `validate:"-" bun:""` // Public comment on this block, viewable (optionally) by everyone
|
||||
Obfuscate *bool `validate:"-" bun:",nullzero,notnull,default:false"` // whether the domain name should appear obfuscated when displaying it publicly
|
||||
SubscriptionID string `validate:"omitempty,ulid" bun:"type:CHAR(26),nullzero"` // if this block was created through a subscription, what's the subscription ID?
|
||||
ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
|
||||
CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
|
||||
UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
|
||||
Domain string `bun:",nullzero,notnull"` // domain to block. Eg. 'whatever.com'
|
||||
CreatedByAccountID string `bun:"type:CHAR(26),nullzero,notnull"` // Account ID of the creator of this block
|
||||
CreatedByAccount *Account `bun:"rel:belongs-to"` // Account corresponding to createdByAccountID
|
||||
PrivateComment string `bun:""` // Private comment on this block, viewable to admins
|
||||
PublicComment string `bun:""` // Public comment on this block, viewable (optionally) by everyone
|
||||
Obfuscate *bool `bun:",nullzero,notnull,default:false"` // whether the domain name should appear obfuscated when displaying it publicly
|
||||
SubscriptionID string `bun:"type:CHAR(26),nullzero"` // if this block was created through a subscription, what's the subscription ID?
|
||||
}
|
||||
|
|
|
@ -21,10 +21,10 @@ import "time"
|
|||
|
||||
// EmailDomainBlock represents a domain that the server should automatically reject sign-up requests from.
|
||||
type EmailDomainBlock struct {
|
||||
ID string `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
|
||||
CreatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
|
||||
UpdatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
|
||||
Domain string `validate:"required,fqdn" bun:",nullzero,notnull"` // Email domain to block. Eg. 'gmail.com' or 'hotmail.com'
|
||||
CreatedByAccountID string `validate:"required,ulid" bun:"type:CHAR(26),nullzero,notnull"` // Account ID of the creator of this block
|
||||
CreatedByAccount *Account `validate:"-" bun:"rel:belongs-to"` // Account corresponding to createdByAccountID
|
||||
ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
|
||||
CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
|
||||
UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
|
||||
Domain string `bun:",nullzero,notnull"` // Email domain to block. Eg. 'gmail.com' or 'hotmail.com'
|
||||
CreatedByAccountID string `bun:"type:CHAR(26),nullzero,notnull"` // Account ID of the creator of this block
|
||||
CreatedByAccount *Account `bun:"rel:belongs-to"` // Account corresponding to createdByAccountID
|
||||
}
|
||||
|
|
|
@ -21,26 +21,26 @@ import "time"
|
|||
|
||||
// Emoji represents a custom emoji that's been uploaded through the admin UI or downloaded from a remote instance.
|
||||
type Emoji struct {
|
||||
ID string `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
|
||||
CreatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
|
||||
UpdatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
|
||||
Shortcode string `validate:"required" bun:",nullzero,notnull,unique:domainshortcode"` // String shortcode for this emoji -- the part that's between colons. This should be a-zA-Z_ eg., 'blob_hug' 'purple_heart' 'Gay_Otter' Must be unique with domain.
|
||||
Domain string `validate:"omitempty,fqdn" bun:",nullzero,unique:domainshortcode"` // Origin domain of this emoji, eg 'example.org', 'queer.party'. empty string for local emojis.
|
||||
ImageRemoteURL string `validate:"required_without=ImageURL,omitempty,url" bun:",nullzero"` // Where can this emoji be retrieved remotely? Null for local emojis.
|
||||
ImageStaticRemoteURL string `validate:"required_without=ImageStaticURL,omitempty,url" bun:",nullzero"` // Where can a static / non-animated version of this emoji be retrieved remotely? Null for local emojis.
|
||||
ImageURL string `validate:"required_without=ImageRemoteURL,required_without=Domain,omitempty,url" bun:",nullzero"` // Where can this emoji be retrieved from the local server? Null for remote emojis.
|
||||
ImageStaticURL string `validate:"required_without=ImageStaticRemoteURL,required_without=Domain,omitempty,url" bun:",nullzero"` // Where can a static version of this emoji be retrieved from the local server? Null for remote emojis.
|
||||
ImagePath string `validate:"required,file" bun:",nullzero,notnull"` // Path of the emoji image in the server storage system.
|
||||
ImageStaticPath string `validate:"required,file" bun:",nullzero,notnull"` // Path of a static version of the emoji image in the server storage system
|
||||
ImageContentType string `validate:"required" bun:",nullzero,notnull"` // MIME content type of the emoji image
|
||||
ImageStaticContentType string `validate:"required" bun:",nullzero,notnull"` // MIME content type of the static version of the emoji image.
|
||||
ImageFileSize int `validate:"required,min=1" bun:",nullzero,notnull"` // Size of the emoji image file in bytes, for serving purposes.
|
||||
ImageStaticFileSize int `validate:"required,min=1" bun:",nullzero,notnull"` // Size of the static version of the emoji image file in bytes, for serving purposes.
|
||||
ImageUpdatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // When was the emoji image last updated?
|
||||
Disabled *bool `validate:"-" bun:",nullzero,notnull,default:false"` // Has a moderation action disabled this emoji from being shown?
|
||||
URI string `validate:"url" bun:",nullzero,notnull,unique"` // ActivityPub uri of this emoji. Something like 'https://example.org/emojis/1234'
|
||||
VisibleInPicker *bool `validate:"-" bun:",nullzero,notnull,default:true"` // Is this emoji visible in the admin emoji picker?
|
||||
Category *EmojiCategory `validate:"-" bun:"rel:belongs-to"` // In which emoji category is this emoji visible?
|
||||
CategoryID string `validate:"omitempty,ulid" bun:"type:CHAR(26),nullzero"` // ID of the category this emoji belongs to.
|
||||
Cached *bool `validate:"-" bun:",nullzero,notnull,default:false"`
|
||||
ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
|
||||
CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
|
||||
UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
|
||||
Shortcode string `bun:",nullzero,notnull,unique:domainshortcode"` // String shortcode for this emoji -- the part that's between colons. This should be a-zA-Z_ eg., 'blob_hug' 'purple_heart' 'Gay_Otter' Must be unique with domain.
|
||||
Domain string `bun:",nullzero,unique:domainshortcode"` // Origin domain of this emoji, eg 'example.org', 'queer.party'. empty string for local emojis.
|
||||
ImageRemoteURL string `bun:",nullzero"` // Where can this emoji be retrieved remotely? Null for local emojis.
|
||||
ImageStaticRemoteURL string `bun:",nullzero"` // Where can a static / non-animated version of this emoji be retrieved remotely? Null for local emojis.
|
||||
ImageURL string `bun:",nullzero"` // Where can this emoji be retrieved from the local server? Null for remote emojis.
|
||||
ImageStaticURL string `bun:",nullzero"` // Where can a static version of this emoji be retrieved from the local server? Null for remote emojis.
|
||||
ImagePath string `bun:",nullzero,notnull"` // Path of the emoji image in the server storage system.
|
||||
ImageStaticPath string `bun:",nullzero,notnull"` // Path of a static version of the emoji image in the server storage system
|
||||
ImageContentType string `bun:",nullzero,notnull"` // MIME content type of the emoji image
|
||||
ImageStaticContentType string `bun:",nullzero,notnull"` // MIME content type of the static version of the emoji image.
|
||||
ImageFileSize int `bun:",nullzero,notnull"` // Size of the emoji image file in bytes, for serving purposes.
|
||||
ImageStaticFileSize int `bun:",nullzero,notnull"` // Size of the static version of the emoji image file in bytes, for serving purposes.
|
||||
ImageUpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // When was the emoji image last updated?
|
||||
Disabled *bool `bun:",nullzero,notnull,default:false"` // Has a moderation action disabled this emoji from being shown?
|
||||
URI string `bun:",nullzero,notnull,unique"` // ActivityPub uri of this emoji. Something like 'https://example.org/emojis/1234'
|
||||
VisibleInPicker *bool `bun:",nullzero,notnull,default:true"` // Is this emoji visible in the admin emoji picker?
|
||||
Category *EmojiCategory `bun:"rel:belongs-to"` // In which emoji category is this emoji visible?
|
||||
CategoryID string `bun:"type:CHAR(26),nullzero"` // ID of the category this emoji belongs to.
|
||||
Cached *bool `bun:",nullzero,notnull,default:false"`
|
||||
}
|
||||
|
|
|
@ -21,8 +21,8 @@ import "time"
|
|||
|
||||
// EmojiCategory represents a grouping of custom emojis.
|
||||
type EmojiCategory struct {
|
||||
ID string `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
|
||||
CreatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
|
||||
UpdatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
|
||||
Name string `validate:"required" bun:",nullzero,notnull,unique"` // name of this category
|
||||
ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
|
||||
CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
|
||||
UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
|
||||
Name string `bun:",nullzero,notnull,unique"` // name of this category
|
||||
}
|
||||
|
|
|
@ -21,14 +21,14 @@ import "time"
|
|||
|
||||
// Follow represents one account following another, and the metadata around that follow.
|
||||
type Follow struct {
|
||||
ID string `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
|
||||
CreatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
|
||||
UpdatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
|
||||
URI string `validate:"required,url" bun:",notnull,nullzero,unique"` // ActivityPub uri of this follow.
|
||||
AccountID string `validate:"required,ulid" bun:"type:CHAR(26),unique:srctarget,notnull,nullzero"` // Who does this follow originate from?
|
||||
Account *Account `validate:"-" bun:"rel:belongs-to"` // Account corresponding to accountID
|
||||
TargetAccountID string `validate:"required,ulid" bun:"type:CHAR(26),unique:srctarget,notnull,nullzero"` // Who is the target of this follow ?
|
||||
TargetAccount *Account `validate:"-" bun:"rel:belongs-to"` // Account corresponding to targetAccountID
|
||||
ShowReblogs *bool `validate:"-" bun:",nullzero,notnull,default:true"` // Does this follow also want to see reblogs and not just posts?
|
||||
Notify *bool `validate:"-" bun:",nullzero,notnull,default:false"` // does the following account want to be notified when the followed account posts?
|
||||
ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
|
||||
CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
|
||||
UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
|
||||
URI string `bun:",notnull,nullzero,unique"` // ActivityPub uri of this follow.
|
||||
AccountID string `bun:"type:CHAR(26),unique:srctarget,notnull,nullzero"` // Who does this follow originate from?
|
||||
Account *Account `bun:"rel:belongs-to"` // Account corresponding to accountID
|
||||
TargetAccountID string `bun:"type:CHAR(26),unique:srctarget,notnull,nullzero"` // Who is the target of this follow ?
|
||||
TargetAccount *Account `bun:"rel:belongs-to"` // Account corresponding to targetAccountID
|
||||
ShowReblogs *bool `bun:",nullzero,notnull,default:true"` // Does this follow also want to see reblogs and not just posts?
|
||||
Notify *bool `bun:",nullzero,notnull,default:false"` // does the following account want to be notified when the followed account posts?
|
||||
}
|
||||
|
|
|
@ -21,14 +21,14 @@ import "time"
|
|||
|
||||
// FollowRequest represents one account requesting to follow another, and the metadata around that request.
|
||||
type FollowRequest struct {
|
||||
ID string `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
|
||||
CreatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
|
||||
UpdatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
|
||||
URI string `validate:"required,url" bun:",notnull,nullzero,unique"` // ActivityPub uri of this follow (request).
|
||||
AccountID string `validate:"required,ulid" bun:"type:CHAR(26),unique:frsrctarget,notnull,nullzero"` // Who does this follow request originate from?
|
||||
Account *Account `validate:"-" bun:"rel:belongs-to"` // Account corresponding to accountID
|
||||
TargetAccountID string `validate:"required,ulid" bun:"type:CHAR(26),unique:frsrctarget,notnull,nullzero"` // Who is the target of this follow request?
|
||||
TargetAccount *Account `validate:"-" bun:"rel:belongs-to"` // Account corresponding to targetAccountID
|
||||
ShowReblogs *bool `validate:"-" bun:",nullzero,notnull,default:true"` // Does this follow also want to see reblogs and not just posts?
|
||||
Notify *bool `validate:"-" bun:",nullzero,notnull,default:false"` // does the following account want to be notified when the followed account posts?
|
||||
ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
|
||||
CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
|
||||
UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
|
||||
URI string `bun:",notnull,nullzero,unique"` // ActivityPub uri of this follow (request).
|
||||
AccountID string `bun:"type:CHAR(26),unique:frsrctarget,notnull,nullzero"` // Who does this follow request originate from?
|
||||
Account *Account `bun:"rel:belongs-to"` // Account corresponding to accountID
|
||||
TargetAccountID string `bun:"type:CHAR(26),unique:frsrctarget,notnull,nullzero"` // Who is the target of this follow request?
|
||||
TargetAccount *Account `bun:"rel:belongs-to"` // Account corresponding to targetAccountID
|
||||
ShowReblogs *bool `bun:",nullzero,notnull,default:true"` // Does this follow also want to see reblogs and not just posts?
|
||||
Notify *bool `bun:",nullzero,notnull,default:false"` // does the following account want to be notified when the followed account posts?
|
||||
}
|
||||
|
|
|
@ -21,22 +21,22 @@ import "time"
|
|||
|
||||
// Instance represents a federated instance, either local or remote.
|
||||
type Instance struct {
|
||||
ID string `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
|
||||
CreatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
|
||||
UpdatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
|
||||
Domain string `validate:"required,fqdn" bun:",nullzero,notnull,unique"` // Instance domain eg example.org
|
||||
Title string `validate:"-" bun:""` // Title of this instance as it would like to be displayed.
|
||||
URI string `validate:"required,url" bun:",nullzero,notnull,unique"` // base URI of this instance eg https://example.org
|
||||
SuspendedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero"` // When was this instance suspended, if at all?
|
||||
DomainBlockID string `validate:"omitempty,ulid" bun:"type:CHAR(26),nullzero"` // ID of any existing domain block for this instance in the database
|
||||
DomainBlock *DomainBlock `validate:"-" bun:"rel:belongs-to"` // Domain block corresponding to domainBlockID
|
||||
ShortDescription string `validate:"-" bun:""` // Short description of this instance
|
||||
Description string `validate:"-" bun:""` // Longer description of this instance
|
||||
Terms string `validate:"-" bun:""` // Terms and conditions of this instance
|
||||
ContactEmail string `validate:"omitempty,email" bun:""` // Contact email address for this instance
|
||||
ContactAccountUsername string `validate:"required_with=ContactAccountID" bun:",nullzero"` // Username of the contact account for this instance
|
||||
ContactAccountID string `validate:"required_with=ContactAccountUsername,omitempty,ulid" bun:"type:CHAR(26),nullzero"` // Contact account ID in the database for this instance
|
||||
ContactAccount *Account `validate:"-" bun:"rel:belongs-to"` // account corresponding to contactAccountID
|
||||
Reputation int64 `validate:"-" bun:",notnull,default:0"` // Reputation score of this instance
|
||||
Version string `validate:"-" bun:",nullzero"` // Version of the software used on this instance
|
||||
ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
|
||||
CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
|
||||
UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
|
||||
Domain string `bun:",nullzero,notnull,unique"` // Instance domain eg example.org
|
||||
Title string `bun:""` // Title of this instance as it would like to be displayed.
|
||||
URI string `bun:",nullzero,notnull,unique"` // base URI of this instance eg https://example.org
|
||||
SuspendedAt time.Time `bun:"type:timestamptz,nullzero"` // When was this instance suspended, if at all?
|
||||
DomainBlockID string `bun:"type:CHAR(26),nullzero"` // ID of any existing domain block for this instance in the database
|
||||
DomainBlock *DomainBlock `bun:"rel:belongs-to"` // Domain block corresponding to domainBlockID
|
||||
ShortDescription string `bun:""` // Short description of this instance
|
||||
Description string `bun:""` // Longer description of this instance
|
||||
Terms string `bun:""` // Terms and conditions of this instance
|
||||
ContactEmail string `bun:""` // Contact email address for this instance
|
||||
ContactAccountUsername string `bun:",nullzero"` // Username of the contact account for this instance
|
||||
ContactAccountID string `bun:"type:CHAR(26),nullzero"` // Contact account ID in the database for this instance
|
||||
ContactAccount *Account `bun:"rel:belongs-to"` // account corresponding to contactAccountID
|
||||
Reputation int64 `bun:",notnull,default:0"` // Reputation score of this instance
|
||||
Version string `bun:",nullzero"` // Version of the software used on this instance
|
||||
}
|
||||
|
|
|
@ -21,24 +21,24 @@ import "time"
|
|||
|
||||
// List refers to a list of follows for which the owning account wants to view a timeline of posts.
|
||||
type List struct {
|
||||
ID string `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
|
||||
CreatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
|
||||
UpdatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
|
||||
Title string `validate:"required" bun:",nullzero,notnull,unique:listaccounttitle"` // Title of this list.
|
||||
AccountID string `validate:"required,ulid" bun:"type:CHAR(26),notnull,nullzero,unique:listaccounttitle"` // Account that created/owns the list
|
||||
Account *Account `validate:"-" bun:"-"` // Account corresponding to accountID
|
||||
ListEntries []*ListEntry `validate:"-" bun:"-"` // Entries contained by this list.
|
||||
RepliesPolicy RepliesPolicy `validate:"-" bun:",nullzero,notnull,default:'followed'"` // RepliesPolicy for this list.
|
||||
ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
|
||||
CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
|
||||
UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
|
||||
Title string `bun:",nullzero,notnull,unique:listaccounttitle"` // Title of this list.
|
||||
AccountID string `bun:"type:CHAR(26),notnull,nullzero,unique:listaccounttitle"` // Account that created/owns the list
|
||||
Account *Account `bun:"-"` // Account corresponding to accountID
|
||||
ListEntries []*ListEntry `bun:"-"` // Entries contained by this list.
|
||||
RepliesPolicy RepliesPolicy `bun:",nullzero,notnull,default:'followed'"` // RepliesPolicy for this list.
|
||||
}
|
||||
|
||||
// ListEntry refers to a single follow entry in a list.
|
||||
type ListEntry struct {
|
||||
ID string `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
|
||||
CreatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
|
||||
UpdatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
|
||||
ListID string `validate:"required,ulid" bun:"type:CHAR(26),notnull,nullzero,unique:listentrylistfollow"` // ID of the list that this entry belongs to.
|
||||
FollowID string `validate:"required,ulid" bun:"type:CHAR(26),notnull,nullzero,unique:listentrylistfollow"` // Follow that the account owning this entry wants to see posts of in the timeline.
|
||||
Follow *Follow `validate:"-" bun:"-"` // Follow corresponding to followID.
|
||||
ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
|
||||
CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
|
||||
UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
|
||||
ListID string `bun:"type:CHAR(26),notnull,nullzero,unique:listentrylistfollow"` // ID of the list that this entry belongs to.
|
||||
FollowID string `bun:"type:CHAR(26),notnull,nullzero,unique:listentrylistfollow"` // Follow that the account owning this entry wants to see posts of in the timeline.
|
||||
Follow *Follow `bun:"-"` // Follow corresponding to followID.
|
||||
}
|
||||
|
||||
// RepliesPolicy denotes which replies should be shown in the list.
|
||||
|
|
|
@ -21,11 +21,11 @@ import "time"
|
|||
|
||||
// Marker stores a local account's read position on a given timeline.
|
||||
type Marker struct {
|
||||
AccountID string `validate:"required,ulid" bun:"type:CHAR(26),pk,unique:markers_account_id_timeline_uniq,notnull,nullzero"` // ID of the local account that owns the marker
|
||||
Name MarkerName `validate:"oneof=home notifications" bun:",nullzero,notnull,pk,unique:markers_account_id_timeline_uniq"` // Name of the marked timeline
|
||||
UpdatedAt time.Time `validate:"required" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // When marker was last updated
|
||||
Version int `validate:"required,min=0" bun:",nullzero,notnull,default:0"` // For optimistic concurrency control
|
||||
LastReadID string `validate:"required,ulid" bun:"type:CHAR(26),notnull,nullzero"` // Last ID read on this timeline (status ID for home, notification ID for notifications)
|
||||
AccountID string `bun:"type:CHAR(26),pk,unique:markers_account_id_timeline_uniq,notnull,nullzero"` // ID of the local account that owns the marker
|
||||
Name MarkerName `bun:",nullzero,notnull,pk,unique:markers_account_id_timeline_uniq"` // Name of the marked timeline
|
||||
UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // When marker was last updated
|
||||
Version int `bun:",nullzero,notnull,default:0"` // For optimistic concurrency control
|
||||
LastReadID string `bun:"type:CHAR(26),notnull,nullzero"` // Last ID read on this timeline (status ID for home, notification ID for notifications)
|
||||
}
|
||||
|
||||
// MarkerName is the name of one of the timelines we can store markers for.
|
||||
|
|
|
@ -24,42 +24,42 @@ import (
|
|||
// MediaAttachment represents a user-uploaded media attachment: an image/video/audio/gif that is
|
||||
// somewhere in storage and that can be retrieved and served by the router.
|
||||
type MediaAttachment struct {
|
||||
ID string `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
|
||||
CreatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
|
||||
UpdatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
|
||||
StatusID string `validate:"omitempty,ulid" bun:"type:CHAR(26),nullzero"` // ID of the status to which this is attached
|
||||
URL string `validate:"required_without=RemoteURL,omitempty,url" bun:",nullzero"` // Where can the attachment be retrieved on *this* server
|
||||
RemoteURL string `validate:"required_without=URL,omitempty,url" bun:",nullzero"` // Where can the attachment be retrieved on a remote server (empty for local media)
|
||||
Type FileType `validate:"oneof=Image Gifv Audio Video Unknown" bun:",nullzero,notnull"` // Type of file (image/gifv/audio/video)
|
||||
FileMeta FileMeta `validate:"required" bun:",embed:,nullzero,notnull"` // Metadata about the file
|
||||
AccountID string `validate:"required,ulid" bun:"type:CHAR(26),nullzero,notnull"` // To which account does this attachment belong
|
||||
Description string `validate:"-" bun:""` // Description of the attachment (for screenreaders)
|
||||
ScheduledStatusID string `validate:"omitempty,ulid" bun:"type:CHAR(26),nullzero"` // To which scheduled status does this attachment belong
|
||||
Blurhash string `validate:"required_if=Type Image,required_if=Type Gif,required_if=Type Video" bun:",nullzero"` // What is the generated blurhash of this attachment
|
||||
Processing ProcessingStatus `validate:"oneof=0 1 2 666" bun:",notnull,default:2"` // What is the processing status of this attachment
|
||||
File File `validate:"required" bun:",embed:file_,notnull,nullzero"` // metadata for the whole file
|
||||
Thumbnail Thumbnail `validate:"required" bun:",embed:thumbnail_,notnull,nullzero"` // small image thumbnail derived from a larger image, video, or audio file.
|
||||
Avatar *bool `validate:"-" bun:",nullzero,notnull,default:false"` // Is this attachment being used as an avatar?
|
||||
Header *bool `validate:"-" bun:",nullzero,notnull,default:false"` // Is this attachment being used as a header?
|
||||
Cached *bool `validate:"-" bun:",nullzero,notnull,default:false"` // Is this attachment currently cached by our instance?
|
||||
ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
|
||||
CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
|
||||
UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
|
||||
StatusID string `bun:"type:CHAR(26),nullzero"` // ID of the status to which this is attached
|
||||
URL string `bun:",nullzero"` // Where can the attachment be retrieved on *this* server
|
||||
RemoteURL string `bun:",nullzero"` // Where can the attachment be retrieved on a remote server (empty for local media)
|
||||
Type FileType `bun:",nullzero,notnull"` // Type of file (image/gifv/audio/video)
|
||||
FileMeta FileMeta `bun:",embed:,nullzero,notnull"` // Metadata about the file
|
||||
AccountID string `bun:"type:CHAR(26),nullzero,notnull"` // To which account does this attachment belong
|
||||
Description string `bun:""` // Description of the attachment (for screenreaders)
|
||||
ScheduledStatusID string `bun:"type:CHAR(26),nullzero"` // To which scheduled status does this attachment belong
|
||||
Blurhash string `bun:",nullzero"` // What is the generated blurhash of this attachment
|
||||
Processing ProcessingStatus `bun:",notnull,default:2"` // What is the processing status of this attachment
|
||||
File File `bun:",embed:file_,notnull,nullzero"` // metadata for the whole file
|
||||
Thumbnail Thumbnail `bun:",embed:thumbnail_,notnull,nullzero"` // small image thumbnail derived from a larger image, video, or audio file.
|
||||
Avatar *bool `bun:",nullzero,notnull,default:false"` // Is this attachment being used as an avatar?
|
||||
Header *bool `bun:",nullzero,notnull,default:false"` // Is this attachment being used as a header?
|
||||
Cached *bool `bun:",nullzero,notnull,default:false"` // Is this attachment currently cached by our instance?
|
||||
}
|
||||
|
||||
// File refers to the metadata for the whole file
|
||||
type File struct {
|
||||
Path string `validate:"required,file" bun:",nullzero,notnull"` // Path of the file in storage.
|
||||
ContentType string `validate:"required" bun:",nullzero,notnull"` // MIME content type of the file.
|
||||
FileSize int `validate:"required" bun:",notnull"` // File size in bytes
|
||||
UpdatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // When was the file last updated.
|
||||
Path string `bun:",nullzero,notnull"` // Path of the file in storage.
|
||||
ContentType string `bun:",nullzero,notnull"` // MIME content type of the file.
|
||||
FileSize int `bun:",notnull"` // File size in bytes
|
||||
UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // When was the file last updated.
|
||||
}
|
||||
|
||||
// Thumbnail refers to a small image thumbnail derived from a larger image, video, or audio file.
|
||||
type Thumbnail struct {
|
||||
Path string `validate:"required,file" bun:",nullzero,notnull"` // Path of the file in storage.
|
||||
ContentType string `validate:"required" bun:",nullzero,notnull"` // MIME content type of the file.
|
||||
FileSize int `validate:"required" bun:",notnull"` // File size in bytes
|
||||
UpdatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // When was the file last updated.
|
||||
URL string `validate:"required_without=RemoteURL,omitempty,url" bun:",nullzero"` // What is the URL of the thumbnail on the local server
|
||||
RemoteURL string `validate:"required_without=URL,omitempty,url" bun:",nullzero"` // What is the remote URL of the thumbnail (empty for local media)
|
||||
Path string `bun:",nullzero,notnull"` // Path of the file in storage.
|
||||
ContentType string `bun:",nullzero,notnull"` // MIME content type of the file.
|
||||
FileSize int `bun:",notnull"` // File size in bytes
|
||||
UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // When was the file last updated.
|
||||
URL string `bun:",nullzero"` // What is the URL of the thumbnail on the local server
|
||||
RemoteURL string `bun:",nullzero"` // What is the remote URL of the thumbnail (empty for local media)
|
||||
}
|
||||
|
||||
// ProcessingStatus refers to how far along in the processing stage the attachment is.
|
||||
|
@ -87,33 +87,33 @@ const (
|
|||
|
||||
// FileMeta describes metadata about the actual contents of the file.
|
||||
type FileMeta struct {
|
||||
Original Original `validate:"required" bun:"embed:original_"`
|
||||
Original Original `bun:"embed:original_"`
|
||||
Small Small `bun:"embed:small_"`
|
||||
Focus Focus `bun:"embed:focus_"`
|
||||
}
|
||||
|
||||
// Small can be used for a thumbnail of any media type
|
||||
type Small struct {
|
||||
Width int `validate:"required_with=Height Size Aspect"` // width in pixels
|
||||
Height int `validate:"required_with=Width Size Aspect"` // height in pixels
|
||||
Size int `validate:"required_with=Width Height Aspect"` // size in pixels (width * height)
|
||||
Aspect float32 `validate:"required_with=Width Height Size"` // aspect ratio (width / height)
|
||||
Width int // width in pixels
|
||||
Height int // height in pixels
|
||||
Size int // size in pixels (width * height)
|
||||
Aspect float32 // aspect ratio (width / height)
|
||||
}
|
||||
|
||||
// Original can be used for original metadata for any media type
|
||||
type Original struct {
|
||||
Width int `validate:"required_with=Height Size Aspect"` // width in pixels
|
||||
Height int `validate:"required_with=Width Size Aspect"` // height in pixels
|
||||
Size int `validate:"required_with=Width Height Aspect"` // size in pixels (width * height)
|
||||
Aspect float32 `validate:"required_with=Width Height Size"` // aspect ratio (width / height)
|
||||
Duration *float32 `validate:"-"` // video-specific: duration of the video in seconds
|
||||
Framerate *float32 `validate:"-"` // video-specific: fps
|
||||
Bitrate *uint64 `validate:"-"` // video-specific: bitrate
|
||||
Width int // width in pixels
|
||||
Height int // height in pixels
|
||||
Size int // size in pixels (width * height)
|
||||
Aspect float32 // aspect ratio (width / height)
|
||||
Duration *float32 // video-specific: duration of the video in seconds
|
||||
Framerate *float32 // video-specific: fps
|
||||
Bitrate *uint64 // video-specific: bitrate
|
||||
}
|
||||
|
||||
// Focus describes the 'center' of the image for display purposes.
|
||||
// X and Y should each be between -1 and 1
|
||||
type Focus struct {
|
||||
X float32 `validate:"omitempty,max=1,min=-1"`
|
||||
Y float32 `validate:"omitempty,max=1,min=-1"`
|
||||
X float32
|
||||
Y float32
|
||||
}
|
||||
|
|
|
@ -24,17 +24,17 @@ import (
|
|||
|
||||
// Mention refers to the 'tagging' or 'mention' of a user within a status.
|
||||
type Mention struct {
|
||||
ID string `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
|
||||
CreatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
|
||||
UpdatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
|
||||
StatusID string `validate:"required,ulid" bun:"type:CHAR(26),nullzero,notnull"` // ID of the status this mention originates from
|
||||
Status *Status `validate:"-" bun:"rel:belongs-to"` // status referred to by statusID
|
||||
OriginAccountID string `validate:"required,ulid" bun:"type:CHAR(26),nullzero,notnull"` // ID of the mention creator account
|
||||
OriginAccountURI string `validate:"url" bun:",nullzero,notnull"` // ActivityPub URI of the originator/creator of the mention
|
||||
OriginAccount *Account `validate:"-" bun:"rel:belongs-to"` // account referred to by originAccountID
|
||||
TargetAccountID string `validate:"required,ulid" bun:"type:CHAR(26),nullzero,notnull"` // Mention target/receiver account ID
|
||||
TargetAccount *Account `validate:"-" bun:"rel:belongs-to"` // account referred to by targetAccountID
|
||||
Silent *bool `validate:"-" bun:",nullzero,notnull,default:false"` // Prevent this mention from generating a notification?
|
||||
ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
|
||||
CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
|
||||
UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
|
||||
StatusID string `bun:"type:CHAR(26),nullzero,notnull"` // ID of the status this mention originates from
|
||||
Status *Status `bun:"rel:belongs-to"` // status referred to by statusID
|
||||
OriginAccountID string `bun:"type:CHAR(26),nullzero,notnull"` // ID of the mention creator account
|
||||
OriginAccountURI string `bun:",nullzero,notnull"` // ActivityPub URI of the originator/creator of the mention
|
||||
OriginAccount *Account `bun:"rel:belongs-to"` // account referred to by originAccountID
|
||||
TargetAccountID string `bun:"type:CHAR(26),nullzero,notnull"` // Mention target/receiver account ID
|
||||
TargetAccount *Account `bun:"rel:belongs-to"` // account referred to by targetAccountID
|
||||
Silent *bool `bun:",nullzero,notnull,default:false"` // Prevent this mention from generating a notification?
|
||||
|
||||
/*
|
||||
NON-DATABASE CONVENIENCE FIELDS
|
||||
|
@ -48,15 +48,15 @@ type Mention struct {
|
|||
// @whatever_username@example.org
|
||||
//
|
||||
// This will not be put in the database, it's just for convenience.
|
||||
NameString string `validate:"-" bun:"-"`
|
||||
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 `validate:"-" bun:"-"`
|
||||
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 `validate:"-" bun:"-"`
|
||||
TargetAccountURL string `bun:"-"`
|
||||
// A pointer to the gtsmodel account of the mentioned account.
|
||||
}
|
||||
|
||||
|
|
|
@ -21,17 +21,17 @@ 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 string `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
|
||||
CreatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
|
||||
UpdatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
|
||||
NotificationType NotificationType `validate:"oneof=follow follow_request mention reblog favourite poll status" bun:",nullzero,notnull"` // Type of this notification
|
||||
TargetAccountID string `validate:"ulid" bun:"type:CHAR(26),nullzero,notnull"` // ID of the account targeted by the notification (ie., who will receive the notification?)
|
||||
TargetAccount *Account `validate:"-" bun:"-"` // Account corresponding to TargetAccountID. Can be nil, always check first + select using ID if necessary.
|
||||
OriginAccountID string `validate:"ulid" bun:"type:CHAR(26),nullzero,notnull"` // ID of the account that performed the action that created the notification.
|
||||
OriginAccount *Account `validate:"-" bun:"-"` // Account corresponding to OriginAccountID. Can be nil, always check first + select using ID if necessary.
|
||||
StatusID string `validate:"required_if=NotificationType mention,required_if=NotificationType reblog,required_if=NotificationType favourite,required_if=NotificationType status,omitempty,ulid" bun:"type:CHAR(26),nullzero"` // If the notification pertains to a status, what is the database ID of that status?
|
||||
Status *Status `validate:"-" bun:"-"` // Status corresponding to StatusID. Can be nil, always check first + select using ID if necessary.
|
||||
Read *bool `validate:"-" bun:",nullzero,notnull,default:false"` // Notification has been seen/read
|
||||
ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
|
||||
CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
|
||||
UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
|
||||
NotificationType NotificationType `bun:",nullzero,notnull"` // Type of this notification
|
||||
TargetAccountID string `bun:"type:CHAR(26),nullzero,notnull"` // ID of the account targeted by the notification (ie., who will receive the notification?)
|
||||
TargetAccount *Account `bun:"-"` // Account corresponding to TargetAccountID. Can be nil, always check first + select using ID if necessary.
|
||||
OriginAccountID string `bun:"type:CHAR(26),nullzero,notnull"` // ID of the account that performed the action that created the notification.
|
||||
OriginAccount *Account `bun:"-"` // Account corresponding to OriginAccountID. Can be nil, always check first + select using ID if necessary.
|
||||
StatusID string `bun:"type:CHAR(26),nullzero"` // If the notification pertains to a status, what is the database ID of that status?
|
||||
Status *Status `bun:"-"` // Status corresponding to StatusID. Can be nil, always check first + select using ID if necessary.
|
||||
Read *bool `bun:",nullzero,notnull,default:false"` // Notification has been seen/read
|
||||
}
|
||||
|
||||
// NotificationType describes the reason/type of this notification.
|
||||
|
|
|
@ -26,20 +26,20 @@ import "time"
|
|||
// or another instance, OR a report that was created remotely (on another instance)
|
||||
// about a user on this instance, and received via the federated (s2s) API.
|
||||
type Report struct {
|
||||
ID string `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
|
||||
CreatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
|
||||
UpdatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
|
||||
URI string `validate:"required,url" bun:",unique,nullzero,notnull"` // activitypub URI of this report
|
||||
AccountID string `validate:"required,ulid" bun:"type:CHAR(26),nullzero,notnull"` // which account created this report
|
||||
Account *Account `validate:"-" bun:"-"` // account corresponding to AccountID
|
||||
TargetAccountID string `validate:"required,ulid" bun:"type:CHAR(26),nullzero,notnull"` // which account is targeted by this report
|
||||
TargetAccount *Account `validate:"-" bun:"-"` // account corresponding to TargetAccountID
|
||||
Comment string `validate:"-" bun:",nullzero"` // comment / explanation for this report, by the reporter
|
||||
StatusIDs []string `validate:"dive,ulid" bun:"statuses,array"` // database IDs of any statuses referenced by this report
|
||||
Statuses []*Status `validate:"-" bun:"-"` // statuses corresponding to StatusIDs
|
||||
Forwarded *bool `validate:"-" bun:",nullzero,notnull,default:false"` // flag to indicate report should be forwarded to remote instance
|
||||
ActionTaken string `validate:"-" bun:",nullzero"` // string description of what action was taken in response to this report
|
||||
ActionTakenAt time.Time `validate:"-" bun:"type:timestamptz,nullzero"` // time at which action was taken, if any
|
||||
ActionTakenByAccountID string `validate:",omitempty,ulid" bun:"type:CHAR(26),nullzero"` // database ID of account which took action, if any
|
||||
ActionTakenByAccount *Account `validate:"-" bun:"-"` // account corresponding to ActionTakenByID, if any
|
||||
ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
|
||||
CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
|
||||
UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
|
||||
URI string `bun:",unique,nullzero,notnull"` // activitypub URI of this report
|
||||
AccountID string `bun:"type:CHAR(26),nullzero,notnull"` // which account created this report
|
||||
Account *Account `bun:"-"` // account corresponding to AccountID
|
||||
TargetAccountID string `bun:"type:CHAR(26),nullzero,notnull"` // which account is targeted by this report
|
||||
TargetAccount *Account `bun:"-"` // account corresponding to TargetAccountID
|
||||
Comment string `bun:",nullzero"` // comment / explanation for this report, by the reporter
|
||||
StatusIDs []string `bun:"statuses,array"` // database IDs of any statuses referenced by this report
|
||||
Statuses []*Status `bun:"-"` // statuses corresponding to StatusIDs
|
||||
Forwarded *bool `bun:",nullzero,notnull,default:false"` // flag to indicate report should be forwarded to remote instance
|
||||
ActionTaken string `bun:",nullzero"` // string description of what action was taken in response to this report
|
||||
ActionTakenAt time.Time `bun:"type:timestamptz,nullzero"` // time at which action was taken, if any
|
||||
ActionTakenByAccountID string `bun:"type:CHAR(26),nullzero"` // database ID of account which took action, if any
|
||||
ActionTakenByAccount *Account `bun:"-"` // account corresponding to ActionTakenByID, if any
|
||||
}
|
||||
|
|
|
@ -21,9 +21,9 @@ import "time"
|
|||
|
||||
// RouterSession is used to store and retrieve settings for a router session.
|
||||
type RouterSession struct {
|
||||
ID string `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
|
||||
CreatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
|
||||
UpdatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
|
||||
Auth []byte `validate:"required,len=32" bun:"type:bytea,notnull,nullzero"`
|
||||
Crypt []byte `validate:"required,len=32" bun:"type:bytea,notnull,nullzero"`
|
||||
ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
|
||||
CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
|
||||
UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
|
||||
Auth []byte `bun:"type:bytea,notnull,nullzero"`
|
||||
Crypt []byte `bun:"type:bytea,notnull,nullzero"`
|
||||
}
|
||||
|
|
|
@ -25,47 +25,47 @@ import (
|
|||
|
||||
// Status represents a user-created 'post' or 'status' in the database, either remote or local
|
||||
type Status struct {
|
||||
ID string `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
|
||||
CreatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
|
||||
UpdatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
|
||||
FetchedAt time.Time `validate:"required_with=!Local" bun:"type:timestamptz,nullzero"` // when was item (remote) last fetched.
|
||||
PinnedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero"` // Status was pinned by owning account at this time.
|
||||
URI string `validate:"required,url" bun:",unique,nullzero,notnull"` // activitypub URI of this status
|
||||
URL string `validate:"url" bun:",nullzero"` // web url for viewing this status
|
||||
Content string `validate:"-" bun:""` // content of this status; likely html-formatted but not guaranteed
|
||||
AttachmentIDs []string `validate:"dive,ulid" bun:"attachments,array"` // Database IDs of any media attachments associated with this status
|
||||
Attachments []*MediaAttachment `validate:"-" bun:"attached_media,rel:has-many"` // Attachments corresponding to attachmentIDs
|
||||
TagIDs []string `validate:"dive,ulid" bun:"tags,array"` // Database IDs of any tags used in this status
|
||||
Tags []*Tag `validate:"-" bun:"attached_tags,m2m:status_to_tags"` // Tags corresponding to tagIDs. https://bun.uptrace.dev/guide/relations.html#many-to-many-relation
|
||||
MentionIDs []string `validate:"dive,ulid" bun:"mentions,array"` // Database IDs of any mentions in this status
|
||||
Mentions []*Mention `validate:"-" bun:"attached_mentions,rel:has-many"` // Mentions corresponding to mentionIDs
|
||||
EmojiIDs []string `validate:"dive,ulid" bun:"emojis,array"` // Database IDs of any emojis used in this status
|
||||
Emojis []*Emoji `validate:"-" bun:"attached_emojis,m2m:status_to_emojis"` // Emojis corresponding to emojiIDs. https://bun.uptrace.dev/guide/relations.html#many-to-many-relation
|
||||
Local *bool `validate:"-" bun:",nullzero,notnull,default:false"` // is this status from a local account?
|
||||
AccountID string `validate:"required,ulid" bun:"type:CHAR(26),nullzero,notnull"` // which account posted this status?
|
||||
Account *Account `validate:"-" bun:"rel:belongs-to"` // account corresponding to accountID
|
||||
AccountURI string `validate:"required,url" bun:",nullzero,notnull"` // activitypub uri of the owner of this status
|
||||
InReplyToID string `validate:"required_with=InReplyToURI InReplyToAccountID,omitempty,ulid" bun:"type:CHAR(26),nullzero"` // id of the status this status replies to
|
||||
InReplyToURI string `validate:"required_with=InReplyToID InReplyToAccountID,omitempty,url" bun:",nullzero"` // activitypub uri of the status this status is a reply to
|
||||
InReplyToAccountID string `validate:"required_with=InReplyToID InReplyToURI,omitempty,ulid" bun:"type:CHAR(26),nullzero"` // id of the account that this status replies to
|
||||
InReplyTo *Status `validate:"-" bun:"-"` // status corresponding to inReplyToID
|
||||
InReplyToAccount *Account `validate:"-" bun:"rel:belongs-to"` // account corresponding to inReplyToAccountID
|
||||
BoostOfID string `validate:"required_with=BoostOfAccountID,omitempty,ulid" bun:"type:CHAR(26),nullzero"` // id of the status this status is a boost of
|
||||
BoostOfAccountID string `validate:"required_with=BoostOfID,omitempty,ulid" bun:"type:CHAR(26),nullzero"` // id of the account that owns the boosted status
|
||||
BoostOf *Status `validate:"-" bun:"-"` // status that corresponds to boostOfID
|
||||
BoostOfAccount *Account `validate:"-" bun:"rel:belongs-to"` // account that corresponds to boostOfAccountID
|
||||
ContentWarning string `validate:"-" bun:",nullzero"` // cw string for this status
|
||||
Visibility Visibility `validate:"oneof=public unlocked followers_only mutuals_only direct" bun:",nullzero,notnull"` // visibility entry for this status
|
||||
Sensitive *bool `validate:"-" bun:",nullzero,notnull,default:false"` // mark the status as sensitive?
|
||||
Language string `validate:"-" bun:",nullzero"` // what language is this status written in?
|
||||
CreatedWithApplicationID string `validate:"required_if=Local true,omitempty,ulid" bun:"type:CHAR(26),nullzero"` // Which application was used to create this status?
|
||||
CreatedWithApplication *Application `validate:"-" bun:"rel:belongs-to"` // application corresponding to createdWithApplicationID
|
||||
ActivityStreamsType string `validate:"required" bun:",nullzero,notnull"` // What is the activitystreams type of this status? See: https://www.w3.org/TR/activitystreams-vocabulary/#object-types. Will probably almost always be Note but who knows!.
|
||||
Text string `validate:"-" bun:""` // Original text of the status without formatting
|
||||
Federated *bool `validate:"-" bun:",notnull"` // This status will be federated beyond the local timeline(s)
|
||||
Boostable *bool `validate:"-" bun:",notnull"` // This status can be boosted/reblogged
|
||||
Replyable *bool `validate:"-" bun:",notnull"` // This status can be replied to
|
||||
Likeable *bool `validate:"-" bun:",notnull"` // This status can be liked/faved
|
||||
ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
|
||||
CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
|
||||
UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
|
||||
FetchedAt time.Time `bun:"type:timestamptz,nullzero"` // when was item (remote) last fetched.
|
||||
PinnedAt time.Time `bun:"type:timestamptz,nullzero"` // Status was pinned by owning account at this time.
|
||||
URI string `bun:",unique,nullzero,notnull"` // activitypub URI of this status
|
||||
URL string `bun:",nullzero"` // web url for viewing this status
|
||||
Content string `bun:""` // content of this status; likely html-formatted but not guaranteed
|
||||
AttachmentIDs []string `bun:"attachments,array"` // Database IDs of any media attachments associated with this status
|
||||
Attachments []*MediaAttachment `bun:"attached_media,rel:has-many"` // Attachments corresponding to attachmentIDs
|
||||
TagIDs []string `bun:"tags,array"` // Database IDs of any tags used in this status
|
||||
Tags []*Tag `bun:"attached_tags,m2m:status_to_tags"` // Tags corresponding to tagIDs. https://bun.uptrace.dev/guide/relations.html#many-to-many-relation
|
||||
MentionIDs []string `bun:"mentions,array"` // Database IDs of any mentions in this status
|
||||
Mentions []*Mention `bun:"attached_mentions,rel:has-many"` // Mentions corresponding to mentionIDs
|
||||
EmojiIDs []string `bun:"emojis,array"` // Database IDs of any emojis used in this status
|
||||
Emojis []*Emoji `bun:"attached_emojis,m2m:status_to_emojis"` // Emojis corresponding to emojiIDs. https://bun.uptrace.dev/guide/relations.html#many-to-many-relation
|
||||
Local *bool `bun:",nullzero,notnull,default:false"` // is this status from a local account?
|
||||
AccountID string `bun:"type:CHAR(26),nullzero,notnull"` // which account posted this status?
|
||||
Account *Account `bun:"rel:belongs-to"` // account corresponding to accountID
|
||||
AccountURI string `bun:",nullzero,notnull"` // activitypub uri of the owner of this status
|
||||
InReplyToID string `bun:"type:CHAR(26),nullzero"` // id of the status this status replies to
|
||||
InReplyToURI string `bun:",nullzero"` // activitypub uri of the status this status is a reply to
|
||||
InReplyToAccountID string `bun:"type:CHAR(26),nullzero"` // id of the account that this status replies to
|
||||
InReplyTo *Status `bun:"-"` // status corresponding to inReplyToID
|
||||
InReplyToAccount *Account `bun:"rel:belongs-to"` // account corresponding to inReplyToAccountID
|
||||
BoostOfID string `bun:"type:CHAR(26),nullzero"` // id of the status this status is a boost of
|
||||
BoostOfAccountID string `bun:"type:CHAR(26),nullzero"` // id of the account that owns the boosted status
|
||||
BoostOf *Status `bun:"-"` // status that corresponds to boostOfID
|
||||
BoostOfAccount *Account `bun:"rel:belongs-to"` // account that corresponds to boostOfAccountID
|
||||
ContentWarning string `bun:",nullzero"` // cw string for this status
|
||||
Visibility Visibility `bun:",nullzero,notnull"` // visibility entry for this status
|
||||
Sensitive *bool `bun:",nullzero,notnull,default:false"` // mark the status as sensitive?
|
||||
Language string `bun:",nullzero"` // what language is this status written in?
|
||||
CreatedWithApplicationID string `bun:"type:CHAR(26),nullzero"` // Which application was used to create this status?
|
||||
CreatedWithApplication *Application `bun:"rel:belongs-to"` // application corresponding to createdWithApplicationID
|
||||
ActivityStreamsType string `bun:",nullzero,notnull"` // What is the activitystreams type of this status? See: https://www.w3.org/TR/activitystreams-vocabulary/#object-types. Will probably almost always be Note but who knows!.
|
||||
Text string `bun:""` // Original text of the status without formatting
|
||||
Federated *bool `bun:",notnull"` // This status will be federated beyond the local timeline(s)
|
||||
Boostable *bool `bun:",notnull"` // This status can be boosted/reblogged
|
||||
Replyable *bool `bun:",notnull"` // This status can be replied to
|
||||
Likeable *bool `bun:",notnull"` // This status can be liked/faved
|
||||
}
|
||||
|
||||
// GetID implements timeline.Timelineable{}.
|
||||
|
@ -252,18 +252,18 @@ func (s *Status) MentionsAccount(id string) bool {
|
|||
|
||||
// StatusToTag is an intermediate struct to facilitate the many2many relationship between a status and one or more tags.
|
||||
type StatusToTag struct {
|
||||
StatusID string `validate:"ulid,required" bun:"type:CHAR(26),unique:statustag,nullzero,notnull"`
|
||||
Status *Status `validate:"-" bun:"rel:belongs-to"`
|
||||
TagID string `validate:"ulid,required" bun:"type:CHAR(26),unique:statustag,nullzero,notnull"`
|
||||
Tag *Tag `validate:"-" bun:"rel:belongs-to"`
|
||||
StatusID string `bun:"type:CHAR(26),unique:statustag,nullzero,notnull"`
|
||||
Status *Status `bun:"rel:belongs-to"`
|
||||
TagID string `bun:"type:CHAR(26),unique:statustag,nullzero,notnull"`
|
||||
Tag *Tag `bun:"rel:belongs-to"`
|
||||
}
|
||||
|
||||
// StatusToEmoji is an intermediate struct to facilitate the many2many relationship between a status and one or more emojis.
|
||||
type StatusToEmoji struct {
|
||||
StatusID string `validate:"ulid,required" bun:"type:CHAR(26),unique:statusemoji,nullzero,notnull"`
|
||||
Status *Status `validate:"-" bun:"rel:belongs-to"`
|
||||
EmojiID string `validate:"ulid,required" bun:"type:CHAR(26),unique:statusemoji,nullzero,notnull"`
|
||||
Emoji *Emoji `validate:"-" bun:"rel:belongs-to"`
|
||||
StatusID string `bun:"type:CHAR(26),unique:statusemoji,nullzero,notnull"`
|
||||
Status *Status `bun:"rel:belongs-to"`
|
||||
EmojiID string `bun:"type:CHAR(26),unique:statusemoji,nullzero,notnull"`
|
||||
Emoji *Emoji `bun:"rel:belongs-to"`
|
||||
}
|
||||
|
||||
// Visibility represents the visibility granularity of a status.
|
||||
|
|
|
@ -21,13 +21,13 @@ import "time"
|
|||
|
||||
// StatusBookmark refers to one account having a 'bookmark' of the status of another account.
|
||||
type StatusBookmark struct {
|
||||
ID string `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
|
||||
CreatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
|
||||
UpdatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
|
||||
AccountID string `validate:"required,ulid" bun:"type:CHAR(26),nullzero,notnull"` // id of the account that created ('did') the bookmark
|
||||
Account *Account `validate:"-" bun:"rel:belongs-to"` // account that created the bookmark
|
||||
TargetAccountID string `validate:"required,ulid" bun:"type:CHAR(26),nullzero,notnull"` // id the account owning the bookmarked status
|
||||
TargetAccount *Account `validate:"-" bun:"rel:belongs-to"` // account owning the bookmarked status
|
||||
StatusID string `validate:"required,ulid" bun:"type:CHAR(26),nullzero,notnull"` // database id of the status that has been bookmarked
|
||||
Status *Status `validate:"-" bun:"rel:belongs-to"` // the bookmarked status
|
||||
ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
|
||||
CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
|
||||
UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
|
||||
AccountID string `bun:"type:CHAR(26),nullzero,notnull"` // id of the account that created ('did') the bookmark
|
||||
Account *Account `bun:"rel:belongs-to"` // account that created the bookmark
|
||||
TargetAccountID string `bun:"type:CHAR(26),nullzero,notnull"` // id the account owning the bookmarked status
|
||||
TargetAccount *Account `bun:"rel:belongs-to"` // account owning the bookmarked status
|
||||
StatusID string `bun:"type:CHAR(26),nullzero,notnull"` // database id of the status that has been bookmarked
|
||||
Status *Status `bun:"rel:belongs-to"` // the bookmarked status
|
||||
}
|
||||
|
|
|
@ -21,14 +21,14 @@ 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 string `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
|
||||
CreatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
|
||||
UpdatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
|
||||
AccountID string `validate:"required,ulid" bun:"type:CHAR(26),unique:statusfaveaccountstatus,nullzero,notnull"` // id of the account that created ('did') the fave
|
||||
Account *Account `validate:"-" bun:"-"` // account that created the fave
|
||||
TargetAccountID string `validate:"required,ulid" bun:"type:CHAR(26),nullzero,notnull"` // id the account owning the faved status
|
||||
TargetAccount *Account `validate:"-" bun:"-"` // account owning the faved status
|
||||
StatusID string `validate:"required,ulid" bun:"type:CHAR(26),unique:statusfaveaccountstatus,nullzero,notnull"` // database id of the status that has been 'faved'
|
||||
Status *Status `validate:"-" bun:"-"` // the faved status
|
||||
URI string `validate:"required,url" bun:",nullzero,notnull,unique"` // ActivityPub URI of this fave
|
||||
ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
|
||||
CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
|
||||
UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
|
||||
AccountID string `bun:"type:CHAR(26),unique:statusfaveaccountstatus,nullzero,notnull"` // id of the account that created ('did') the fave
|
||||
Account *Account `bun:"-"` // account that created the fave
|
||||
TargetAccountID string `bun:"type:CHAR(26),nullzero,notnull"` // id the account owning the faved status
|
||||
TargetAccount *Account `bun:"-"` // account owning the faved status
|
||||
StatusID string `bun:"type:CHAR(26),unique:statusfaveaccountstatus,nullzero,notnull"` // database id of the status that has been 'faved'
|
||||
Status *Status `bun:"-"` // the faved status
|
||||
URI string `bun:",nullzero,notnull,unique"` // ActivityPub URI of this fave
|
||||
}
|
||||
|
|
|
@ -21,13 +21,13 @@ import "time"
|
|||
|
||||
// StatusMute refers to one account having muted the status of another account or its own.
|
||||
type StatusMute struct {
|
||||
ID string `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
|
||||
CreatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
|
||||
UpdatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
|
||||
AccountID string `validate:"required,ulid" bun:"type:CHAR(26),nullzero,notnull"` // id of the account that created ('did') the mute
|
||||
Account *Account `validate:"-" bun:"rel:belongs-to"` // pointer to the account specified by accountID
|
||||
TargetAccountID string `validate:"required,ulid" bun:"type:CHAR(26),nullzero,notnull"` // id the account owning the muted status (can be the same as accountID)
|
||||
TargetAccount *Account `validate:"-" bun:"rel:belongs-to"` // pointer to the account specified by targetAccountID
|
||||
StatusID string `validate:"required,ulid" bun:"type:CHAR(26),nullzero,notnull"` // database id of the status that has been muted
|
||||
Status *Status `validate:"-" bun:"rel:belongs-to"` // pointer to the muted status specified by statusID
|
||||
ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
|
||||
CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
|
||||
UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
|
||||
AccountID string `bun:"type:CHAR(26),nullzero,notnull"` // id of the account that created ('did') the mute
|
||||
Account *Account `bun:"rel:belongs-to"` // pointer to the account specified by accountID
|
||||
TargetAccountID string `bun:"type:CHAR(26),nullzero,notnull"` // id the account owning the muted status (can be the same as accountID)
|
||||
TargetAccount *Account `bun:"rel:belongs-to"` // pointer to the account specified by targetAccountID
|
||||
StatusID string `bun:"type:CHAR(26),nullzero,notnull"` // database id of the status that has been muted
|
||||
Status *Status `bun:"rel:belongs-to"` // pointer to the muted status specified by statusID
|
||||
}
|
||||
|
|
|
@ -21,10 +21,10 @@ import "time"
|
|||
|
||||
// Tag represents a hashtag for gathering public statuses together.
|
||||
type Tag struct {
|
||||
ID string `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
|
||||
CreatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
|
||||
UpdatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
|
||||
Name string `validate:"required" bun:",unique,nullzero,notnull"` // (lowercase) name of the tag without the hash prefix
|
||||
Useable *bool `validate:"-" bun:",nullzero,notnull,default:true"` // Tag is useable on this instance.
|
||||
Listable *bool `validate:"-" bun:",nullzero,notnull,default:true"` // Tagged statuses can be listed on this instance.
|
||||
ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
|
||||
CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
|
||||
UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
|
||||
Name string `bun:",unique,nullzero,notnull"` // (lowercase) name of the tag without the hash prefix
|
||||
Useable *bool `bun:",nullzero,notnull,default:true"` // Tag is useable on this instance.
|
||||
Listable *bool `bun:",nullzero,notnull,default:true"` // Tagged statuses can be listed on this instance.
|
||||
}
|
||||
|
|
|
@ -21,22 +21,22 @@ import "time"
|
|||
|
||||
// Token is a translation of the gotosocial token with the ExpiresIn fields replaced with ExpiresAt.
|
||||
type Token struct {
|
||||
ID string `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
|
||||
CreatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
|
||||
UpdatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
|
||||
ClientID string `validate:"required,ulid" bun:"type:CHAR(26),nullzero,notnull"` // ID of the client who owns this token
|
||||
UserID string `validate:"required,ulid" bun:"type:CHAR(26),nullzero"` // ID of the user who owns this token
|
||||
RedirectURI string `validate:"required,uri" bun:",nullzero,notnull"` // Oauth redirect URI for this token
|
||||
Scope string `validate:"required" bun:",notnull"` // Oauth scope
|
||||
Code string `validate:"-" bun:",pk,nullzero,notnull,default:''"` // Code, if present
|
||||
CodeChallenge string `validate:"-" bun:",nullzero"` // Code challenge, if code present
|
||||
CodeChallengeMethod string `validate:"-" bun:",nullzero"` // Code challenge method, if code present
|
||||
CodeCreateAt time.Time `validate:"required_with=Code" bun:"type:timestamptz,nullzero"` // Code created time, if code present
|
||||
CodeExpiresAt time.Time `validate:"-" bun:"type:timestamptz,nullzero"` // Code expires at -- null means the code never expires
|
||||
Access string `validate:"-" bun:",pk,nullzero,notnull,default:''"` // User level access token, if present
|
||||
AccessCreateAt time.Time `validate:"required_with=Access" bun:"type:timestamptz,nullzero"` // User level access token created time, if access present
|
||||
AccessExpiresAt time.Time `validate:"-" bun:"type:timestamptz,nullzero"` // User level access token expires at -- null means the token never expires
|
||||
Refresh string `validate:"-" bun:",pk,nullzero,notnull,default:''"` // Refresh token, if present
|
||||
RefreshCreateAt time.Time `validate:"required_with=Refresh" bun:"type:timestamptz,nullzero"` // Refresh created at, if refresh present
|
||||
RefreshExpiresAt time.Time `validate:"-" bun:"type:timestamptz,nullzero"` // Refresh expires at -- null means the refresh token never expires
|
||||
ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
|
||||
CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
|
||||
UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
|
||||
ClientID string `bun:"type:CHAR(26),nullzero,notnull"` // ID of the client who owns this token
|
||||
UserID string `bun:"type:CHAR(26),nullzero"` // ID of the user who owns this token
|
||||
RedirectURI string `bun:",nullzero,notnull"` // Oauth redirect URI for this token
|
||||
Scope string `bun:",notnull"` // Oauth scope
|
||||
Code string `bun:",pk,nullzero,notnull,default:''"` // Code, if present
|
||||
CodeChallenge string `bun:",nullzero"` // Code challenge, if code present
|
||||
CodeChallengeMethod string `bun:",nullzero"` // Code challenge method, if code present
|
||||
CodeCreateAt time.Time `bun:"type:timestamptz,nullzero"` // Code created time, if code present
|
||||
CodeExpiresAt time.Time `bun:"type:timestamptz,nullzero"` // Code expires at -- null means the code never expires
|
||||
Access string `bun:",pk,nullzero,notnull,default:''"` // User level access token, if present
|
||||
AccessCreateAt time.Time `bun:"type:timestamptz,nullzero"` // User level access token created time, if access present
|
||||
AccessExpiresAt time.Time `bun:"type:timestamptz,nullzero"` // User level access token expires at -- null means the token never expires
|
||||
Refresh string `bun:",pk,nullzero,notnull,default:''"` // Refresh token, if present
|
||||
RefreshCreateAt time.Time `bun:"type:timestamptz,nullzero"` // Refresh created at, if refresh present
|
||||
RefreshExpiresAt time.Time `bun:"type:timestamptz,nullzero"` // Refresh expires at -- null means the refresh token never expires
|
||||
}
|
||||
|
|
|
@ -29,9 +29,9 @@ import (
|
|||
// It's useful in cases where a remote account has been deleted, and we don't want to keep trying to process
|
||||
// subsequent activities from that account, or deletes which target it.
|
||||
type Tombstone struct {
|
||||
ID string `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
|
||||
CreatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
|
||||
UpdatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
|
||||
Domain string `validate:"omitempty,fqdn" bun:",nullzero,notnull"` // Domain of the Object/Actor.
|
||||
URI string `validate:"required,url" bun:",nullzero,notnull,unique"` // ActivityPub URI for this Object/Actor.
|
||||
ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
|
||||
CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
|
||||
UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
|
||||
Domain string `bun:",nullzero,notnull"` // Domain of the Object/Actor.
|
||||
URI string `bun:",nullzero,notnull,unique"` // ActivityPub URI for this Object/Actor.
|
||||
}
|
||||
|
|
|
@ -25,35 +25,35 @@ import (
|
|||
// User represents an actual human user of gotosocial. Note, this is a LOCAL gotosocial user, not a remote account.
|
||||
// To cross reference this local user with their account (which can be local or remote), use the AccountID field.
|
||||
type User struct {
|
||||
ID string `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
|
||||
CreatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
|
||||
UpdatedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
|
||||
Email string `validate:"required_with=ConfirmedAt" bun:",nullzero,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
|
||||
AccountID string `validate:"required,ulid" bun:"type:CHAR(26),nullzero,notnull,unique"` // The id of the local gtsmodel.Account entry for this user.
|
||||
Account *Account `validate:"-" bun:"rel:belongs-to"` // Pointer to the account of this user that corresponds to AccountID.
|
||||
EncryptedPassword string `validate:"required" bun:",nullzero,notnull"` // 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.
|
||||
SignUpIP net.IP `validate:"-" bun:",nullzero"` // From what IP was this user created?
|
||||
CurrentSignInAt time.Time `validate:"-" bun:"type:timestamptz,nullzero"` // When did the user sign in with their current session.
|
||||
CurrentSignInIP net.IP `validate:"-" bun:",nullzero"` // What's the most recent IP of this user
|
||||
LastSignInAt time.Time `validate:"-" bun:"type:timestamptz,nullzero"` // When did this user last sign in?
|
||||
LastSignInIP net.IP `validate:"-" bun:",nullzero"` // What's the previous IP of this user?
|
||||
SignInCount int `validate:"min=0" bun:",notnull,default:0"` // How many times has this user signed in?
|
||||
InviteID string `validate:"omitempty,ulid" bun:"type:CHAR(26),nullzero"` // id of the user who invited this user (who let this joker in?)
|
||||
ChosenLanguages []string `validate:"-" bun:",nullzero"` // What languages does this user want to see?
|
||||
FilteredLanguages []string `validate:"-" bun:",nullzero"` // What languages does this user not want to see?
|
||||
Locale string `validate:"-" bun:",nullzero"` // In what timezone/locale is this user located?
|
||||
CreatedByApplicationID string `validate:"omitempty,ulid" bun:"type:CHAR(26),nullzero"` // Which application id created this user? See gtsmodel.Application
|
||||
CreatedByApplication *Application `validate:"-" bun:"rel:belongs-to"` // Pointer to the application corresponding to createdbyapplicationID.
|
||||
LastEmailedAt time.Time `validate:"-" bun:"type:timestamptz,nullzero"` // When was this user last contacted by email.
|
||||
ConfirmationToken string `validate:"required_with=ConfirmationSentAt" bun:",nullzero"` // What confirmation token did we send this user/what are we expecting back?
|
||||
ConfirmationSentAt time.Time `validate:"required_with=ConfirmationToken" bun:"type:timestamptz,nullzero"` // When did we send email confirmation to this user?
|
||||
ConfirmedAt time.Time `validate:"required_with=Email" bun:"type:timestamptz,nullzero"` // When did the user confirm their email address
|
||||
UnconfirmedEmail string `validate:"required_without=Email" bun:",nullzero"` // Email address that hasn't yet been confirmed
|
||||
Moderator *bool `validate:"-" bun:",nullzero,notnull,default:false"` // Is this user a moderator?
|
||||
Admin *bool `validate:"-" bun:",nullzero,notnull,default:false"` // Is this user an admin?
|
||||
Disabled *bool `validate:"-" bun:",nullzero,notnull,default:false"` // Is this user disabled from posting?
|
||||
Approved *bool `validate:"-" bun:",nullzero,notnull,default:false"` // Has this user been approved by a moderator?
|
||||
ResetPasswordToken string `validate:"required_with=ResetPasswordSentAt" bun:",nullzero"` // The generated token that the user can use to reset their password
|
||||
ResetPasswordSentAt time.Time `validate:"required_with=ResetPasswordToken" bun:"type:timestamptz,nullzero"` // When did we email the user their reset-password email?
|
||||
ExternalID string `validate:"-" bun:",nullzero,unique"` // If the login for the user is managed externally (e.g OIDC), we need to keep a stable reference to the external object (e.g OIDC sub claim)
|
||||
ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database
|
||||
CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created
|
||||
UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
|
||||
Email string `bun:",nullzero,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
|
||||
AccountID string `bun:"type:CHAR(26),nullzero,notnull,unique"` // The id of the local gtsmodel.Account entry for this user.
|
||||
Account *Account `bun:"rel:belongs-to"` // Pointer to the account of this user that corresponds to AccountID.
|
||||
EncryptedPassword string `bun:",nullzero,notnull"` // 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.
|
||||
SignUpIP net.IP `bun:",nullzero"` // From what IP was this user created?
|
||||
CurrentSignInAt time.Time `bun:"type:timestamptz,nullzero"` // When did the user sign in with their current session.
|
||||
CurrentSignInIP net.IP `bun:",nullzero"` // What's the most recent IP of this user
|
||||
LastSignInAt time.Time `bun:"type:timestamptz,nullzero"` // When did this user last sign in?
|
||||
LastSignInIP net.IP `bun:",nullzero"` // What's the previous IP of this user?
|
||||
SignInCount int `bun:",notnull,default:0"` // How many times has this user signed in?
|
||||
InviteID string `bun:"type:CHAR(26),nullzero"` // id of the user who invited this user (who let this joker in?)
|
||||
ChosenLanguages []string `bun:",nullzero"` // What languages does this user want to see?
|
||||
FilteredLanguages []string `bun:",nullzero"` // What languages does this user not want to see?
|
||||
Locale string `bun:",nullzero"` // In what timezone/locale is this user located?
|
||||
CreatedByApplicationID string `bun:"type:CHAR(26),nullzero"` // Which application id created this user? See gtsmodel.Application
|
||||
CreatedByApplication *Application `bun:"rel:belongs-to"` // Pointer to the application corresponding to createdbyapplicationID.
|
||||
LastEmailedAt time.Time `bun:"type:timestamptz,nullzero"` // When was this user last contacted by email.
|
||||
ConfirmationToken string `bun:",nullzero"` // What confirmation token did we send this user/what are we expecting back?
|
||||
ConfirmationSentAt time.Time `bun:"type:timestamptz,nullzero"` // When did we send email confirmation to this user?
|
||||
ConfirmedAt time.Time `bun:"type:timestamptz,nullzero"` // When did the user confirm their email address
|
||||
UnconfirmedEmail string `bun:",nullzero"` // Email address that hasn't yet been confirmed
|
||||
Moderator *bool `bun:",nullzero,notnull,default:false"` // Is this user a moderator?
|
||||
Admin *bool `bun:",nullzero,notnull,default:false"` // Is this user an admin?
|
||||
Disabled *bool `bun:",nullzero,notnull,default:false"` // Is this user disabled from posting?
|
||||
Approved *bool `bun:",nullzero,notnull,default:false"` // Has this user been approved by a moderator?
|
||||
ResetPasswordToken string `bun:",nullzero"` // The generated token that the user can use to reset their password
|
||||
ResetPasswordSentAt time.Time `bun:"type:timestamptz,nullzero"` // When did we email the user their reset-password email?
|
||||
ExternalID string `bun:",nullzero,unique"` // If the login for the user is managed externally (e.g OIDC), we need to keep a stable reference to the external object (e.g OIDC sub claim)
|
||||
}
|
||||
|
|
|
@ -247,8 +247,8 @@ func (c *Client) DoSigned(r *http.Request, sign SignFunc) (rsp *http.Response, e
|
|||
|
||||
// Rewind body reader and content-length if set.
|
||||
if rc, ok := r.Body.(*byteutil.ReadNopCloser); ok {
|
||||
rc.Rewind() // set len AFTER rewind
|
||||
r.ContentLength = int64(rc.Len())
|
||||
rc.Rewind()
|
||||
}
|
||||
|
||||
// Sign the outgoing request.
|
||||
|
|
|
@ -34,8 +34,7 @@ import (
|
|||
// Create processes the given form for creating a new account,
|
||||
// returning an oauth token for that account if successful.
|
||||
//
|
||||
// Fields on the form should have already been validated by the
|
||||
// caller, before this function is called.
|
||||
// Precondition: the form's fields should have already been validated and normalized by the caller.
|
||||
func (p *Processor) Create(
|
||||
ctx context.Context,
|
||||
appToken oauth2.TokenInfo,
|
||||
|
|
|
@ -31,6 +31,7 @@ import (
|
|||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/log"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/messages"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
|
@ -427,10 +428,8 @@ func (p *Processor) deleteAccountPeripheral(ctx context.Context, account *gtsmod
|
|||
// names of all columns that are updated by it.
|
||||
func stubbifyAccount(account *gtsmodel.Account, origin string) []string {
|
||||
var (
|
||||
falseBool = func() *bool { b := false; return &b }
|
||||
trueBool = func() *bool { b := true; return &b }
|
||||
now = time.Now()
|
||||
never = time.Time{}
|
||||
now = time.Now()
|
||||
never = time.Time{}
|
||||
)
|
||||
|
||||
account.FetchedAt = never
|
||||
|
@ -444,17 +443,17 @@ func stubbifyAccount(account *gtsmodel.Account, origin string) []string {
|
|||
account.Fields = nil
|
||||
account.Note = ""
|
||||
account.NoteRaw = ""
|
||||
account.Memorial = falseBool()
|
||||
account.Memorial = util.Ptr(false)
|
||||
account.AlsoKnownAs = ""
|
||||
account.MovedToAccountID = ""
|
||||
account.Reason = ""
|
||||
account.Discoverable = falseBool()
|
||||
account.Discoverable = util.Ptr(false)
|
||||
account.StatusContentType = ""
|
||||
account.CustomCSS = ""
|
||||
account.SuspendedAt = now
|
||||
account.SuspensionOrigin = origin
|
||||
account.HideCollections = trueBool()
|
||||
account.EnableRSS = falseBool()
|
||||
account.HideCollections = util.Ptr(true)
|
||||
account.EnableRSS = util.Ptr(false)
|
||||
|
||||
return []string{
|
||||
"fetched_at",
|
||||
|
|
|
@ -23,7 +23,7 @@ import (
|
|||
|
||||
"github.com/stretchr/testify/suite"
|
||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
)
|
||||
|
||||
type FollowTestSuite struct {
|
||||
|
@ -40,10 +40,9 @@ func (suite *FollowTestSuite) TestUpdateExistingFollowChangeBoth() {
|
|||
// UPDATE "follows" AS "follow" SET "show_reblogs" = FALSE, "notify" = TRUE, "updated_at" = '2023-04-09 11:42:39.424705+00:00' WHERE ("follow"."id" = '01F8PY8RHWRQZV038T4E8T9YK8')
|
||||
relationship, err := suite.accountProcessor.FollowCreate(ctx, requestingAccount, &apimodel.AccountFollowRequest{
|
||||
ID: targetAccount.ID,
|
||||
Reblogs: testrig.FalseBool(),
|
||||
Notify: testrig.TrueBool(),
|
||||
Reblogs: util.Ptr(false),
|
||||
Notify: util.Ptr(true),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
@ -62,9 +61,8 @@ func (suite *FollowTestSuite) TestUpdateExistingFollowChangeNotifyIgnoreReblogs(
|
|||
// UPDATE "follows" AS "follow" SET "notify" = TRUE, "updated_at" = '2023-04-09 11:40:33.827858+00:00' WHERE ("follow"."id" = '01F8PY8RHWRQZV038T4E8T9YK8')
|
||||
relationship, err := suite.accountProcessor.FollowCreate(ctx, requestingAccount, &apimodel.AccountFollowRequest{
|
||||
ID: targetAccount.ID,
|
||||
Notify: testrig.TrueBool(),
|
||||
Notify: util.Ptr(true),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
@ -83,10 +81,9 @@ func (suite *FollowTestSuite) TestUpdateExistingFollowChangeNotifySetReblogs() {
|
|||
// UPDATE "follows" AS "follow" SET "notify" = TRUE, "updated_at" = '2023-04-09 11:40:33.827858+00:00' WHERE ("follow"."id" = '01F8PY8RHWRQZV038T4E8T9YK8')
|
||||
relationship, err := suite.accountProcessor.FollowCreate(ctx, requestingAccount, &apimodel.AccountFollowRequest{
|
||||
ID: targetAccount.ID,
|
||||
Notify: testrig.TrueBool(),
|
||||
Reblogs: testrig.TrueBool(),
|
||||
Notify: util.Ptr(true),
|
||||
Reblogs: util.Ptr(true),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
@ -104,10 +101,9 @@ func (suite *FollowTestSuite) TestUpdateExistingFollowChangeNothing() {
|
|||
// Trace logs should show no update query.
|
||||
relationship, err := suite.accountProcessor.FollowCreate(ctx, requestingAccount, &apimodel.AccountFollowRequest{
|
||||
ID: targetAccount.ID,
|
||||
Notify: testrig.FalseBool(),
|
||||
Reblogs: testrig.TrueBool(),
|
||||
Notify: util.Ptr(false),
|
||||
Reblogs: util.Ptr(true),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
@ -126,7 +122,6 @@ func (suite *FollowTestSuite) TestUpdateExistingFollowSetNothing() {
|
|||
relationship, err := suite.accountProcessor.FollowCreate(ctx, requestingAccount, &apimodel.AccountFollowRequest{
|
||||
ID: targetAccount.ID,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
|
|
@ -222,10 +222,11 @@ func (p *Processor) Update(ctx context.Context, account *gtsmodel.Account, form
|
|||
|
||||
if form.Source != nil {
|
||||
if form.Source.Language != nil {
|
||||
if err := validate.Language(*form.Source.Language); err != nil {
|
||||
language, err := validate.Language(*form.Source.Language)
|
||||
if err != nil {
|
||||
return nil, gtserror.NewErrorBadRequest(err)
|
||||
}
|
||||
account.Language = *form.Source.Language
|
||||
account.Language = language
|
||||
}
|
||||
|
||||
if form.Source.Sensitive != nil {
|
||||
|
|
|
@ -121,6 +121,15 @@ func (p *Processor) UserGet(ctx context.Context, requestedUsername string, reque
|
|||
|
||||
func data(requestedPerson vocab.ActivityStreamsPerson) (interface{}, gtserror.WithCode) {
|
||||
data, err := ap.Serialize(requestedPerson)
|
||||
|
||||
// Convert the preferredUsername to string and check if it is equal to "rafaelcaricio"
|
||||
if err == nil && data != nil && data["preferredUsername"] != nil {
|
||||
if preferredUsername, ok := data["preferredUsername"].(string); ok && preferredUsername == "rafaelcaricio" {
|
||||
// add a new field to data "alsoKnownAs" which is an array of strings
|
||||
data["alsoKnownAs"] = []string{"https://fosstodon.org/users/rafaelcaricio"}
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
err := gtserror.Newf("error serializing person: %w", err)
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
|
|
|
@ -30,6 +30,7 @@ import (
|
|||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/messages"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/stream"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
)
|
||||
|
||||
|
@ -63,20 +64,20 @@ func (suite *FromClientAPITestSuite) TestProcessStreamNewStatus() {
|
|||
EmojiIDs: []string{},
|
||||
CreatedAt: testrig.TimeMustParse("2021-10-20T11:36:45Z"),
|
||||
UpdatedAt: testrig.TimeMustParse("2021-10-20T11:36:45Z"),
|
||||
Local: testrig.TrueBool(),
|
||||
Local: util.Ptr(true),
|
||||
AccountURI: "http://localhost:8080/users/admin",
|
||||
AccountID: "01F8MH17FWEB39HZJ76B6VXSKF",
|
||||
InReplyToID: "",
|
||||
BoostOfID: "",
|
||||
ContentWarning: "",
|
||||
Visibility: gtsmodel.VisibilityFollowersOnly,
|
||||
Sensitive: testrig.FalseBool(),
|
||||
Sensitive: util.Ptr(false),
|
||||
Language: "en",
|
||||
CreatedWithApplicationID: "01F8MGXQRHYF5QPMTMXP78QC2F",
|
||||
Federated: testrig.FalseBool(),
|
||||
Boostable: testrig.TrueBool(),
|
||||
Replyable: testrig.TrueBool(),
|
||||
Likeable: testrig.TrueBool(),
|
||||
Federated: util.Ptr(false),
|
||||
Boostable: util.Ptr(true),
|
||||
Replyable: util.Ptr(true),
|
||||
Likeable: util.Ptr(true),
|
||||
ActivityStreamsType: ap.ObjectNote,
|
||||
}
|
||||
|
||||
|
@ -189,7 +190,7 @@ func (suite *FromClientAPITestSuite) TestProcessNewStatusWithNotification() {
|
|||
// that receiving account wants notifs when posting account posts.
|
||||
follow := >smodel.Follow{}
|
||||
*follow = *suite.testFollows["local_account_1_admin_account"]
|
||||
follow.Notify = testrig.TrueBool()
|
||||
follow.Notify = util.Ptr(true)
|
||||
if err := suite.db.UpdateFollow(ctx, follow); err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
@ -206,20 +207,20 @@ func (suite *FromClientAPITestSuite) TestProcessNewStatusWithNotification() {
|
|||
EmojiIDs: []string{},
|
||||
CreatedAt: testrig.TimeMustParse("2021-10-20T11:36:45Z"),
|
||||
UpdatedAt: testrig.TimeMustParse("2021-10-20T11:36:45Z"),
|
||||
Local: testrig.TrueBool(),
|
||||
Local: util.Ptr(true),
|
||||
AccountURI: "http://localhost:8080/users/admin",
|
||||
AccountID: "01F8MH17FWEB39HZJ76B6VXSKF",
|
||||
InReplyToID: "",
|
||||
BoostOfID: "",
|
||||
ContentWarning: "",
|
||||
Visibility: gtsmodel.VisibilityFollowersOnly,
|
||||
Sensitive: testrig.FalseBool(),
|
||||
Sensitive: util.Ptr(false),
|
||||
Language: "en",
|
||||
CreatedWithApplicationID: "01F8MGXQRHYF5QPMTMXP78QC2F",
|
||||
Federated: testrig.FalseBool(),
|
||||
Boostable: testrig.TrueBool(),
|
||||
Replyable: testrig.TrueBool(),
|
||||
Likeable: testrig.TrueBool(),
|
||||
Federated: util.Ptr(false),
|
||||
Boostable: util.Ptr(true),
|
||||
Replyable: util.Ptr(true),
|
||||
Likeable: util.Ptr(true),
|
||||
ActivityStreamsType: ap.ObjectNote,
|
||||
}
|
||||
|
||||
|
|
|
@ -108,20 +108,23 @@ func (p *Processor) ProcessFromFederator(ctx context.Context, federatorMsg messa
|
|||
|
||||
// processCreateStatusFromFederator handles Activity Create and Object Note.
|
||||
func (p *Processor) processCreateStatusFromFederator(ctx context.Context, federatorMsg messages.FromFederator) error {
|
||||
// Check the federatorMsg for either an already
|
||||
// dereferenced and converted status pinned to
|
||||
// the message, or an AP IRI that we need to deref.
|
||||
var (
|
||||
status *gtsmodel.Status
|
||||
err error
|
||||
|
||||
// Check the federatorMsg for either an already dereferenced
|
||||
// and converted status pinned to the message, or a forwarded
|
||||
// AP IRI that we still need to deref.
|
||||
forwarded = (federatorMsg.GTSModel == nil)
|
||||
)
|
||||
|
||||
if federatorMsg.GTSModel != nil {
|
||||
// Model is set, use that.
|
||||
status, err = p.statusFromGTSModel(ctx, federatorMsg)
|
||||
} else {
|
||||
// Model is not set, use IRI.
|
||||
if forwarded {
|
||||
// Model was not set, deref with IRI.
|
||||
// This will also cause the status to be inserted into the db.
|
||||
status, err = p.statusFromAPIRI(ctx, federatorMsg)
|
||||
} else {
|
||||
// Model is set, ensure we have the most up-to-date model.
|
||||
status, err = p.statusFromGTSModel(ctx, federatorMsg)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
|
|
|
@ -32,6 +32,7 @@ import (
|
|||
"github.com/superseriousbusiness/gotosocial/internal/id"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/messages"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/stream"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
)
|
||||
|
||||
|
@ -110,10 +111,10 @@ func (suite *FromFederatorTestSuite) TestProcessReplyMention() {
|
|||
InReplyToAccountID: repliedAccount.ID,
|
||||
Visibility: gtsmodel.VisibilityUnlocked,
|
||||
ActivityStreamsType: ap.ObjectNote,
|
||||
Federated: testrig.TrueBool(),
|
||||
Boostable: testrig.TrueBool(),
|
||||
Replyable: testrig.TrueBool(),
|
||||
Likeable: testrig.FalseBool(),
|
||||
Federated: util.Ptr(true),
|
||||
Boostable: util.Ptr(true),
|
||||
Replyable: util.Ptr(true),
|
||||
Likeable: util.Ptr(false),
|
||||
}
|
||||
|
||||
wssStream, errWithCode := suite.processor.Stream().Open(context.Background(), repliedAccount, stream.TimelineHome)
|
||||
|
@ -317,9 +318,9 @@ func (suite *FromFederatorTestSuite) TestProcessAccountDelete() {
|
|||
UpdatedAt: time.Now().Add(-1 * time.Hour),
|
||||
AccountID: deletedAccount.ID,
|
||||
TargetAccountID: receivingAccount.ID,
|
||||
ShowReblogs: testrig.TrueBool(),
|
||||
ShowReblogs: util.Ptr(true),
|
||||
URI: fmt.Sprintf("%s/follows/01FGRY72ASHBSET64353DPHK9T", deletedAccount.URI),
|
||||
Notify: testrig.FalseBool(),
|
||||
Notify: util.Ptr(false),
|
||||
}
|
||||
err := suite.db.Put(ctx, zorkFollowSatan)
|
||||
suite.NoError(err)
|
||||
|
@ -330,9 +331,9 @@ func (suite *FromFederatorTestSuite) TestProcessAccountDelete() {
|
|||
UpdatedAt: time.Now().Add(-1 * time.Hour),
|
||||
AccountID: receivingAccount.ID,
|
||||
TargetAccountID: deletedAccount.ID,
|
||||
ShowReblogs: testrig.TrueBool(),
|
||||
ShowReblogs: util.Ptr(true),
|
||||
URI: fmt.Sprintf("%s/follows/01FGRYAVAWWPP926J175QGM0WV", receivingAccount.URI),
|
||||
Notify: testrig.FalseBool(),
|
||||
Notify: util.Ptr(false),
|
||||
}
|
||||
err = suite.db.Put(ctx, satanFollowZork)
|
||||
suite.NoError(err)
|
||||
|
@ -405,9 +406,9 @@ func (suite *FromFederatorTestSuite) TestProcessFollowRequestLocked() {
|
|||
Account: originAccount,
|
||||
TargetAccountID: targetAccount.ID,
|
||||
TargetAccount: targetAccount,
|
||||
ShowReblogs: testrig.TrueBool(),
|
||||
ShowReblogs: util.Ptr(true),
|
||||
URI: fmt.Sprintf("%s/follows/01FGRYAVAWWPP926J175QGM0WV", originAccount.URI),
|
||||
Notify: testrig.FalseBool(),
|
||||
Notify: util.Ptr(false),
|
||||
}
|
||||
|
||||
err := suite.db.Put(ctx, satanFollowRequestTurtle)
|
||||
|
@ -462,9 +463,9 @@ func (suite *FromFederatorTestSuite) TestProcessFollowRequestUnlocked() {
|
|||
Account: originAccount,
|
||||
TargetAccountID: targetAccount.ID,
|
||||
TargetAccount: targetAccount,
|
||||
ShowReblogs: testrig.TrueBool(),
|
||||
ShowReblogs: util.Ptr(true),
|
||||
URI: fmt.Sprintf("%s/follows/01FGRYAVAWWPP926J175QGM0WV", originAccount.URI),
|
||||
Notify: testrig.FalseBool(),
|
||||
Notify: util.Ptr(false),
|
||||
}
|
||||
|
||||
err := suite.db.Put(ctx, satanFollowRequestTurtle)
|
||||
|
|
|
@ -28,6 +28,7 @@ import (
|
|||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/media"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
)
|
||||
|
||||
|
@ -68,7 +69,7 @@ func (suite *GetFileTestSuite) TestGetRemoteFileUncached() {
|
|||
|
||||
// uncache the file from local
|
||||
testAttachment := suite.testAttachments["remote_account_1_status_1_attachment_1"]
|
||||
testAttachment.Cached = testrig.FalseBool()
|
||||
testAttachment.Cached = util.Ptr(false)
|
||||
err := suite.db.UpdateByID(ctx, testAttachment, testAttachment.ID, "cached")
|
||||
suite.NoError(err)
|
||||
err = suite.storage.Delete(ctx, testAttachment.File.Path)
|
||||
|
@ -120,7 +121,7 @@ func (suite *GetFileTestSuite) TestGetRemoteFileUncachedInterrupted() {
|
|||
|
||||
// uncache the file from local
|
||||
testAttachment := suite.testAttachments["remote_account_1_status_1_attachment_1"]
|
||||
testAttachment.Cached = testrig.FalseBool()
|
||||
testAttachment.Cached = util.Ptr(false)
|
||||
err := suite.db.UpdateByID(ctx, testAttachment, testAttachment.ID, "cached")
|
||||
suite.NoError(err)
|
||||
err = suite.storage.Delete(ctx, testAttachment.File.Path)
|
||||
|
@ -177,7 +178,7 @@ func (suite *GetFileTestSuite) TestGetRemoteFileThumbnailUncached() {
|
|||
suite.NoError(err)
|
||||
|
||||
// uncache the file from local
|
||||
testAttachment.Cached = testrig.FalseBool()
|
||||
testAttachment.Cached = util.Ptr(false)
|
||||
err = suite.db.UpdateByID(ctx, testAttachment, testAttachment.ID, "cached")
|
||||
suite.NoError(err)
|
||||
err = suite.storage.Delete(ctx, testAttachment.File.Path)
|
||||
|
|
|
@ -37,6 +37,8 @@ import (
|
|||
)
|
||||
|
||||
// Create processes the given form to create a new status, returning the api model representation of that status if it's OK.
|
||||
//
|
||||
// Precondition: the form's fields should have already been validated and normalized by the caller.
|
||||
func (p *Processor) Create(ctx context.Context, account *gtsmodel.Account, application *gtsmodel.Application, form *apimodel.AdvancedStatusCreateForm) (*apimodel.Status, gtserror.WithCode) {
|
||||
accountURIs := uris.GenerateURIsForAccount(account.Username)
|
||||
thisStatusID := id.NewULID()
|
||||
|
@ -55,7 +57,6 @@ func (p *Processor) Create(ctx context.Context, account *gtsmodel.Account, appli
|
|||
ContentWarning: text.SanitizePlaintext(form.SpoilerText),
|
||||
ActivityStreamsType: ap.ObjectNote,
|
||||
Sensitive: &sensitive,
|
||||
Language: form.Language,
|
||||
CreatedWithApplicationID: application.ID,
|
||||
Text: form.Status,
|
||||
}
|
||||
|
|
|
@ -208,6 +208,40 @@ func (suite *StatusCreateTestSuite) TestProcessMediaDescriptionTooShort() {
|
|||
suite.Nil(apiStatus)
|
||||
}
|
||||
|
||||
func (suite *StatusCreateTestSuite) TestProcessLanguageWithScriptPart() {
|
||||
ctx := context.Background()
|
||||
|
||||
creatingAccount := suite.testAccounts["local_account_1"]
|
||||
creatingApplication := suite.testApplications["application_1"]
|
||||
|
||||
statusCreateForm := &apimodel.AdvancedStatusCreateForm{
|
||||
StatusCreateRequest: apimodel.StatusCreateRequest{
|
||||
Status: "你好世界", // hello world
|
||||
MediaIDs: []string{},
|
||||
Poll: nil,
|
||||
InReplyToID: "",
|
||||
Sensitive: false,
|
||||
SpoilerText: "",
|
||||
Visibility: apimodel.VisibilityPublic,
|
||||
ScheduledAt: "",
|
||||
Language: "zh-Hans",
|
||||
ContentType: apimodel.StatusContentTypePlain,
|
||||
},
|
||||
AdvancedVisibilityFlagsForm: apimodel.AdvancedVisibilityFlagsForm{
|
||||
Federated: nil,
|
||||
Boostable: nil,
|
||||
Replyable: nil,
|
||||
Likeable: nil,
|
||||
},
|
||||
}
|
||||
|
||||
apiStatus, err := suite.status.Create(ctx, creatingAccount, creatingApplication, statusCreateForm)
|
||||
suite.NoError(err)
|
||||
suite.NotNil(apiStatus)
|
||||
|
||||
suite.Equal("zh-Hans", *apiStatus.Language)
|
||||
}
|
||||
|
||||
func TestStatusCreateTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(StatusCreateTestSuite))
|
||||
}
|
||||
|
|
|
@ -21,8 +21,6 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/ap"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
|
@ -31,6 +29,9 @@ import (
|
|||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/log"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/uris"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (c *converter) ASRepresentationToAccount(ctx context.Context, accountable ap.Accountable, accountDomain string) (*gtsmodel.Account, error) {
|
||||
|
@ -282,7 +283,7 @@ func (c *converter) ASStatusToStatus(ctx context.Context, statusable ap.Statusab
|
|||
//
|
||||
// Hashtags for later dereferencing.
|
||||
if hashtags, err := ap.ExtractHashtags(statusable); err != nil {
|
||||
l.Infof("error extracting hashtags: %q", err)
|
||||
l.Warnf("error extracting hashtags: %v", err)
|
||||
} else {
|
||||
status.Tags = hashtags
|
||||
}
|
||||
|
@ -291,7 +292,7 @@ func (c *converter) ASStatusToStatus(ctx context.Context, statusable ap.Statusab
|
|||
//
|
||||
// Custom emojis for later dereferencing.
|
||||
if emojis, err := ap.ExtractEmojis(statusable); err != nil {
|
||||
l.Infof("error extracting emojis: %q", err)
|
||||
l.Warnf("error extracting emojis: %v", err)
|
||||
} else {
|
||||
status.Emojis = emojis
|
||||
}
|
||||
|
@ -300,7 +301,7 @@ func (c *converter) ASStatusToStatus(ctx context.Context, statusable ap.Statusab
|
|||
//
|
||||
// Mentions of other accounts for later dereferencing.
|
||||
if mentions, err := ap.ExtractMentions(statusable); err != nil {
|
||||
l.Infof("error extracting mentions: %q", err)
|
||||
l.Warnf("error extracting mentions: %v", err)
|
||||
} else {
|
||||
status.Mentions = mentions
|
||||
}
|
||||
|
@ -321,7 +322,7 @@ func (c *converter) ASStatusToStatus(ctx context.Context, statusable ap.Statusab
|
|||
// db defaults, will fall back to now if not set.
|
||||
published, err := ap.ExtractPublished(statusable)
|
||||
if err != nil {
|
||||
l.Infof("error extracting published: %q", err)
|
||||
l.Warnf("error extracting published: %v", err)
|
||||
} else {
|
||||
status.CreatedAt = published
|
||||
status.UpdatedAt = published
|
||||
|
@ -396,11 +397,10 @@ func (c *converter) ASStatusToStatus(ctx context.Context, statusable ap.Statusab
|
|||
// TODO: a lot of work to be done here -- a new type
|
||||
// needs to be created for this in go-fed/activity.
|
||||
// Until this is implemented, assume all true.
|
||||
var trueBool = func() *bool { b := true; return &b }
|
||||
status.Federated = trueBool()
|
||||
status.Boostable = trueBool()
|
||||
status.Replyable = trueBool()
|
||||
status.Likeable = trueBool()
|
||||
status.Federated = util.Ptr(true)
|
||||
status.Boostable = util.Ptr(true)
|
||||
status.Replyable = util.Ptr(true)
|
||||
status.Likeable = util.Ptr(true)
|
||||
|
||||
// status.Sensitive
|
||||
status.Sensitive = func() *bool {
|
||||
|
@ -630,8 +630,8 @@ func (c *converter) ASAnnounceToStatus(ctx context.Context, announceable ap.Anno
|
|||
// Extract published time for the boost.
|
||||
published, err := ap.ExtractPublished(announceable)
|
||||
if err != nil {
|
||||
err = gtserror.Newf("error extracting published: %w", err)
|
||||
return nil, isNew, err
|
||||
//err = gtserror.Newf("error extracting published: %w", err)
|
||||
published = time.Now().UTC()
|
||||
}
|
||||
status.CreatedAt = published
|
||||
status.UpdatedAt = published
|
||||
|
|
23
internal/util/ptr.go
Normal file
23
internal/util/ptr.go
Normal file
|
@ -0,0 +1,23 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// 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 util
|
||||
|
||||
// Ptr returns a pointer to the passed in type
|
||||
func Ptr[T any](t T) *T {
|
||||
return &t
|
||||
}
|
|
@ -1,343 +0,0 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// 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 validate_test
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/ap"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/validate"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
)
|
||||
|
||||
func happyAccount() *gtsmodel.Account {
|
||||
priv, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
pub := &priv.PublicKey
|
||||
|
||||
return >smodel.Account{
|
||||
ID: "01F8MH1H7YV1Z7D2C8K2730QBF",
|
||||
CreatedAt: time.Now().Add(-48 * time.Hour),
|
||||
UpdatedAt: time.Now().Add(-48 * time.Hour),
|
||||
Username: "the_mighty_zork",
|
||||
Domain: "",
|
||||
AvatarMediaAttachmentID: "01F8MH58A357CV5K7R7TJMSH6S",
|
||||
AvatarMediaAttachment: nil,
|
||||
AvatarRemoteURL: "",
|
||||
HeaderMediaAttachmentID: "01PFPMWK2FF0D9WMHEJHR07C3Q",
|
||||
HeaderMediaAttachment: nil,
|
||||
HeaderRemoteURL: "",
|
||||
DisplayName: "original zork (he/they)",
|
||||
Fields: []*gtsmodel.Field{},
|
||||
Note: "hey yo this is my profile!",
|
||||
Memorial: testrig.FalseBool(),
|
||||
AlsoKnownAs: "",
|
||||
MovedToAccountID: "",
|
||||
Bot: testrig.FalseBool(),
|
||||
Reason: "I wanna be on this damned webbed site so bad! Please! Wow",
|
||||
Locked: testrig.FalseBool(),
|
||||
Discoverable: testrig.TrueBool(),
|
||||
Privacy: gtsmodel.VisibilityPublic,
|
||||
Sensitive: testrig.FalseBool(),
|
||||
Language: "en",
|
||||
StatusContentType: "text/plain",
|
||||
URI: "http://localhost:8080/users/the_mighty_zork",
|
||||
URL: "http://localhost:8080/@the_mighty_zork",
|
||||
FetchedAt: time.Time{},
|
||||
InboxURI: "http://localhost:8080/users/the_mighty_zork/inbox",
|
||||
OutboxURI: "http://localhost:8080/users/the_mighty_zork/outbox",
|
||||
FollowersURI: "http://localhost:8080/users/the_mighty_zork/followers",
|
||||
FollowingURI: "http://localhost:8080/users/the_mighty_zork/following",
|
||||
FeaturedCollectionURI: "http://localhost:8080/users/the_mighty_zork/collections/featured",
|
||||
ActorType: ap.ActorPerson,
|
||||
PrivateKey: priv,
|
||||
PublicKey: pub,
|
||||
PublicKeyURI: "http://localhost:8080/users/the_mighty_zork#main-key",
|
||||
SensitizedAt: time.Time{},
|
||||
SilencedAt: time.Time{},
|
||||
SuspendedAt: time.Time{},
|
||||
HideCollections: testrig.FalseBool(),
|
||||
SuspensionOrigin: "",
|
||||
}
|
||||
}
|
||||
|
||||
type AccountValidateTestSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func (suite *AccountValidateTestSuite) TestValidateAccountHappyPath() {
|
||||
// no problem here
|
||||
a := happyAccount()
|
||||
err := validate.Struct(*a)
|
||||
suite.NoError(err)
|
||||
}
|
||||
|
||||
// ID must be set and be valid ULID
|
||||
func (suite *AccountValidateTestSuite) TestValidateAccountBadID() {
|
||||
a := happyAccount()
|
||||
|
||||
a.ID = ""
|
||||
err := validate.Struct(*a)
|
||||
suite.EqualError(err, "Key: 'Account.ID' Error:Field validation for 'ID' failed on the 'required' tag")
|
||||
|
||||
a.ID = "01FE96W293ZPRG9FQQP48HK8N001FE96W32AT24VYBGM12WN3GKB"
|
||||
err = validate.Struct(*a)
|
||||
suite.EqualError(err, "Key: 'Account.ID' Error:Field validation for 'ID' failed on the 'ulid' tag")
|
||||
}
|
||||
|
||||
// CreatedAt can be set or not -- it will be set in the database anyway
|
||||
func (suite *AccountValidateTestSuite) TestValidateAccountNoCreatedAt() {
|
||||
a := happyAccount()
|
||||
|
||||
a.CreatedAt = time.Time{}
|
||||
err := validate.Struct(*a)
|
||||
suite.NoError(err)
|
||||
}
|
||||
|
||||
// FetchedAt must be defined if remote account
|
||||
func (suite *AccountValidateTestSuite) TestValidateAccountNoWebfingeredAt() {
|
||||
a := happyAccount()
|
||||
|
||||
a.Domain = "example.org"
|
||||
a.FetchedAt = time.Time{}
|
||||
err := validate.Struct(*a)
|
||||
suite.EqualError(err, "Key: 'Account.FetchedAt' Error:Field validation for 'FetchedAt' failed on the 'required_with' tag")
|
||||
}
|
||||
|
||||
// Username must be set
|
||||
func (suite *AccountValidateTestSuite) TestValidateAccountUsername() {
|
||||
a := happyAccount()
|
||||
|
||||
a.Username = ""
|
||||
err := validate.Struct(*a)
|
||||
suite.EqualError(err, "Key: 'Account.Username' Error:Field validation for 'Username' failed on the 'required' tag")
|
||||
}
|
||||
|
||||
// Domain must be either empty (for local accounts) or proper fqdn (for remote accounts)
|
||||
func (suite *AccountValidateTestSuite) TestValidateAccountDomain() {
|
||||
a := happyAccount()
|
||||
a.FetchedAt = time.Now()
|
||||
|
||||
a.Domain = ""
|
||||
err := validate.Struct(*a)
|
||||
suite.NoError(err)
|
||||
|
||||
a.Domain = "localhost:8080"
|
||||
err = validate.Struct(*a)
|
||||
suite.EqualError(err, "Key: 'Account.Domain' Error:Field validation for 'Domain' failed on the 'fqdn' tag")
|
||||
|
||||
a.Domain = "ahhhhh"
|
||||
err = validate.Struct(*a)
|
||||
suite.EqualError(err, "Key: 'Account.Domain' Error:Field validation for 'Domain' failed on the 'fqdn' tag")
|
||||
|
||||
a.Domain = "https://www.example.org"
|
||||
err = validate.Struct(*a)
|
||||
suite.EqualError(err, "Key: 'Account.Domain' Error:Field validation for 'Domain' failed on the 'fqdn' tag")
|
||||
|
||||
a.Domain = "example.org:8080"
|
||||
err = validate.Struct(*a)
|
||||
suite.EqualError(err, "Key: 'Account.Domain' Error:Field validation for 'Domain' failed on the 'fqdn' tag")
|
||||
|
||||
a.Domain = "example.org"
|
||||
err = validate.Struct(*a)
|
||||
suite.NoError(err)
|
||||
}
|
||||
|
||||
// Attachment IDs must either be not set, or must be valid ULID
|
||||
func (suite *AccountValidateTestSuite) TestValidateAttachmentIDs() {
|
||||
a := happyAccount()
|
||||
|
||||
a.AvatarMediaAttachmentID = ""
|
||||
a.HeaderMediaAttachmentID = ""
|
||||
err := validate.Struct(*a)
|
||||
suite.NoError(err)
|
||||
|
||||
a.AvatarMediaAttachmentID = "01FE96W293ZPRG9FQQP48HK8N001FE96W32AT24VYBGM12WN3GKB"
|
||||
a.HeaderMediaAttachmentID = "aaaa"
|
||||
err = validate.Struct(*a)
|
||||
suite.EqualError(err, "Key: 'Account.AvatarMediaAttachmentID' Error:Field validation for 'AvatarMediaAttachmentID' failed on the 'ulid' tag\nKey: 'Account.HeaderMediaAttachmentID' Error:Field validation for 'HeaderMediaAttachmentID' failed on the 'ulid' tag")
|
||||
}
|
||||
|
||||
// Attachment remote URLs must either not be set, or be valid URLs
|
||||
func (suite *AccountValidateTestSuite) TestValidateAttachmentRemoteURLs() {
|
||||
a := happyAccount()
|
||||
|
||||
a.AvatarRemoteURL = ""
|
||||
a.HeaderRemoteURL = ""
|
||||
err := validate.Struct(*a)
|
||||
suite.NoError(err)
|
||||
|
||||
a.AvatarRemoteURL = "-------------"
|
||||
a.HeaderRemoteURL = "https://valid-url.com"
|
||||
err = validate.Struct(*a)
|
||||
suite.EqualError(err, "Key: 'Account.AvatarRemoteURL' Error:Field validation for 'AvatarRemoteURL' failed on the 'url' tag")
|
||||
|
||||
a.AvatarRemoteURL = "https://valid-url.com"
|
||||
a.HeaderRemoteURL = ""
|
||||
err = validate.Struct(*a)
|
||||
suite.NoError(err)
|
||||
}
|
||||
|
||||
// Default privacy must be set if account is local
|
||||
func (suite *AccountValidateTestSuite) TestValidatePrivacy() {
|
||||
a := happyAccount()
|
||||
a.FetchedAt = time.Now()
|
||||
|
||||
a.Privacy = ""
|
||||
err := validate.Struct(*a)
|
||||
suite.EqualError(err, "Key: 'Account.Privacy' Error:Field validation for 'Privacy' failed on the 'required_without' tag")
|
||||
|
||||
a.Privacy = "not valid"
|
||||
err = validate.Struct(*a)
|
||||
suite.EqualError(err, "Key: 'Account.Privacy' Error:Field validation for 'Privacy' failed on the 'oneof' tag")
|
||||
|
||||
a.Privacy = gtsmodel.VisibilityFollowersOnly
|
||||
err = validate.Struct(*a)
|
||||
suite.NoError(err)
|
||||
|
||||
a.Privacy = ""
|
||||
a.Domain = "example.org"
|
||||
err = validate.Struct(*a)
|
||||
suite.NoError(err)
|
||||
|
||||
a.Privacy = "invalid"
|
||||
err = validate.Struct(*a)
|
||||
suite.EqualError(err, "Key: 'Account.Privacy' Error:Field validation for 'Privacy' failed on the 'oneof' tag")
|
||||
}
|
||||
|
||||
// If set, language must be a valid language
|
||||
func (suite *AccountValidateTestSuite) TestValidateLanguage() {
|
||||
a := happyAccount()
|
||||
|
||||
a.Language = ""
|
||||
err := validate.Struct(*a)
|
||||
suite.NoError(err)
|
||||
|
||||
a.Language = "not valid"
|
||||
err = validate.Struct(*a)
|
||||
suite.EqualError(err, "Key: 'Account.Language' Error:Field validation for 'Language' failed on the 'bcp47_language_tag' tag")
|
||||
|
||||
a.Language = "en-uk"
|
||||
err = validate.Struct(*a)
|
||||
suite.NoError(err)
|
||||
}
|
||||
|
||||
// Account URI must be set and must be valid
|
||||
func (suite *AccountValidateTestSuite) TestValidateAccountURI() {
|
||||
a := happyAccount()
|
||||
|
||||
a.URI = "invalid-uri"
|
||||
err := validate.Struct(*a)
|
||||
suite.EqualError(err, "Key: 'Account.URI' Error:Field validation for 'URI' failed on the 'url' tag")
|
||||
|
||||
a.URI = ""
|
||||
err = validate.Struct(*a)
|
||||
suite.EqualError(err, "Key: 'Account.URI' Error:Field validation for 'URI' failed on the 'required' tag")
|
||||
}
|
||||
|
||||
// ActivityPub URIs must be set on account if it's local
|
||||
func (suite *AccountValidateTestSuite) TestValidateAccountURIs() {
|
||||
a := happyAccount()
|
||||
a.FetchedAt = time.Now()
|
||||
|
||||
a.InboxURI = "invalid-uri"
|
||||
a.OutboxURI = "invalid-uri"
|
||||
a.FollowersURI = "invalid-uri"
|
||||
a.FollowingURI = "invalid-uri"
|
||||
a.FeaturedCollectionURI = "invalid-uri"
|
||||
a.PublicKeyURI = "invalid-uri"
|
||||
err := validate.Struct(*a)
|
||||
suite.EqualError(err, "Key: 'Account.InboxURI' Error:Field validation for 'InboxURI' failed on the 'url' tag\nKey: 'Account.OutboxURI' Error:Field validation for 'OutboxURI' failed on the 'url' tag\nKey: 'Account.FollowingURI' Error:Field validation for 'FollowingURI' failed on the 'url' tag\nKey: 'Account.FollowersURI' Error:Field validation for 'FollowersURI' failed on the 'url' tag\nKey: 'Account.FeaturedCollectionURI' Error:Field validation for 'FeaturedCollectionURI' failed on the 'url' tag\nKey: 'Account.PublicKeyURI' Error:Field validation for 'PublicKeyURI' failed on the 'url' tag")
|
||||
|
||||
a.InboxURI = ""
|
||||
a.OutboxURI = ""
|
||||
a.FollowersURI = ""
|
||||
a.FollowingURI = ""
|
||||
a.FeaturedCollectionURI = ""
|
||||
a.PublicKeyURI = ""
|
||||
err = validate.Struct(*a)
|
||||
suite.EqualError(err, "Key: 'Account.InboxURI' Error:Field validation for 'InboxURI' failed on the 'required_without' tag\nKey: 'Account.OutboxURI' Error:Field validation for 'OutboxURI' failed on the 'required_without' tag\nKey: 'Account.FollowingURI' Error:Field validation for 'FollowingURI' failed on the 'required_without' tag\nKey: 'Account.FollowersURI' Error:Field validation for 'FollowersURI' failed on the 'required_without' tag\nKey: 'Account.FeaturedCollectionURI' Error:Field validation for 'FeaturedCollectionURI' failed on the 'required_without' tag\nKey: 'Account.PublicKeyURI' Error:Field validation for 'PublicKeyURI' failed on the 'required' tag")
|
||||
|
||||
a.Domain = "example.org"
|
||||
err = validate.Struct(*a)
|
||||
suite.EqualError(err, "Key: 'Account.PublicKeyURI' Error:Field validation for 'PublicKeyURI' failed on the 'required' tag")
|
||||
|
||||
a.InboxURI = "invalid-uri"
|
||||
a.OutboxURI = "invalid-uri"
|
||||
a.FollowersURI = "invalid-uri"
|
||||
a.FollowingURI = "invalid-uri"
|
||||
a.FeaturedCollectionURI = "invalid-uri"
|
||||
a.PublicKeyURI = "invalid-uri"
|
||||
err = validate.Struct(*a)
|
||||
suite.EqualError(err, "Key: 'Account.InboxURI' Error:Field validation for 'InboxURI' failed on the 'url' tag\nKey: 'Account.OutboxURI' Error:Field validation for 'OutboxURI' failed on the 'url' tag\nKey: 'Account.FollowingURI' Error:Field validation for 'FollowingURI' failed on the 'url' tag\nKey: 'Account.FollowersURI' Error:Field validation for 'FollowersURI' failed on the 'url' tag\nKey: 'Account.FeaturedCollectionURI' Error:Field validation for 'FeaturedCollectionURI' failed on the 'url' tag\nKey: 'Account.PublicKeyURI' Error:Field validation for 'PublicKeyURI' failed on the 'url' tag")
|
||||
}
|
||||
|
||||
// Actor type must be set and valid
|
||||
func (suite *AccountValidateTestSuite) TestValidateActorType() {
|
||||
a := happyAccount()
|
||||
|
||||
a.ActorType = ""
|
||||
err := validate.Struct(*a)
|
||||
suite.EqualError(err, "Key: 'Account.ActorType' Error:Field validation for 'ActorType' failed on the 'oneof' tag")
|
||||
|
||||
a.ActorType = "not valid"
|
||||
err = validate.Struct(*a)
|
||||
suite.EqualError(err, "Key: 'Account.ActorType' Error:Field validation for 'ActorType' failed on the 'oneof' tag")
|
||||
|
||||
a.ActorType = ap.ActivityArrive
|
||||
err = validate.Struct(*a)
|
||||
suite.EqualError(err, "Key: 'Account.ActorType' Error:Field validation for 'ActorType' failed on the 'oneof' tag")
|
||||
|
||||
a.ActorType = ap.ActorOrganization
|
||||
err = validate.Struct(*a)
|
||||
suite.NoError(err)
|
||||
}
|
||||
|
||||
// Private key must be set on local accounts
|
||||
func (suite *AccountValidateTestSuite) TestValidatePrivateKey() {
|
||||
a := happyAccount()
|
||||
a.FetchedAt = time.Now()
|
||||
|
||||
a.PrivateKey = nil
|
||||
err := validate.Struct(*a)
|
||||
suite.EqualError(err, "Key: 'Account.PrivateKey' Error:Field validation for 'PrivateKey' failed on the 'required_without' tag")
|
||||
|
||||
a.Domain = "example.org"
|
||||
err = validate.Struct(*a)
|
||||
suite.NoError(err)
|
||||
}
|
||||
|
||||
// Public key must be set
|
||||
func (suite *AccountValidateTestSuite) TestValidatePublicKey() {
|
||||
a := happyAccount()
|
||||
|
||||
a.PublicKey = nil
|
||||
err := validate.Struct(*a)
|
||||
suite.EqualError(err, "Key: 'Account.PublicKey' Error:Field validation for 'PublicKey' failed on the 'required' tag")
|
||||
}
|
||||
|
||||
func TestAccountValidateTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(AccountValidateTestSuite))
|
||||
}
|
|
@ -1,132 +0,0 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// 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 validate_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/validate"
|
||||
)
|
||||
|
||||
func happyApplication() *gtsmodel.Application {
|
||||
return >smodel.Application{
|
||||
ID: "01FE91RJR88PSEEE30EV35QR8N",
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
Name: "Tusky",
|
||||
Website: "https://tusky.app",
|
||||
RedirectURI: "oauth2redirect://com.keylesspalace.tusky/",
|
||||
ClientID: "01FEEDMF6C0QD589MRK7919Z0R",
|
||||
ClientSecret: "bd740cf1-024a-4e4d-8c39-866538f52fe6",
|
||||
Scopes: "read write follow",
|
||||
}
|
||||
}
|
||||
|
||||
type ApplicationValidateTestSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func (suite *ApplicationValidateTestSuite) TestValidateApplicationHappyPath() {
|
||||
// no problem here
|
||||
a := happyApplication()
|
||||
err := validate.Struct(a)
|
||||
suite.NoError(err)
|
||||
}
|
||||
|
||||
func (suite *ApplicationValidateTestSuite) TestValidateApplicationBadID() {
|
||||
a := happyApplication()
|
||||
|
||||
a.ID = ""
|
||||
err := validate.Struct(a)
|
||||
suite.EqualError(err, "Key: 'Application.ID' Error:Field validation for 'ID' failed on the 'required' tag")
|
||||
|
||||
a.ID = "01FE96W293ZPRG9FQQP48HK8N001FE96W32AT24VYBGM12WN3GKB"
|
||||
err = validate.Struct(a)
|
||||
suite.EqualError(err, "Key: 'Application.ID' Error:Field validation for 'ID' failed on the 'ulid' tag")
|
||||
}
|
||||
|
||||
func (suite *ApplicationValidateTestSuite) TestValidateApplicationNoCreatedAt() {
|
||||
a := happyApplication()
|
||||
|
||||
a.CreatedAt = time.Time{}
|
||||
err := validate.Struct(a)
|
||||
suite.NoError(err)
|
||||
}
|
||||
|
||||
func (suite *ApplicationValidateTestSuite) TestValidateApplicationName() {
|
||||
a := happyApplication()
|
||||
|
||||
a.Name = ""
|
||||
err := validate.Struct(a)
|
||||
suite.EqualError(err, "Key: 'Application.Name' Error:Field validation for 'Name' failed on the 'required' tag")
|
||||
}
|
||||
|
||||
func (suite *ApplicationValidateTestSuite) TestValidateApplicationWebsite() {
|
||||
a := happyApplication()
|
||||
|
||||
a.Website = "invalid-website"
|
||||
err := validate.Struct(a)
|
||||
suite.EqualError(err, "Key: 'Application.Website' Error:Field validation for 'Website' failed on the 'url' tag")
|
||||
|
||||
a.Website = ""
|
||||
err = validate.Struct(a)
|
||||
suite.NoError(err)
|
||||
}
|
||||
|
||||
func (suite *ApplicationValidateTestSuite) TestValidateApplicationRedirectURI() {
|
||||
a := happyApplication()
|
||||
|
||||
a.RedirectURI = "invalid-uri"
|
||||
err := validate.Struct(a)
|
||||
suite.EqualError(err, "Key: 'Application.RedirectURI' Error:Field validation for 'RedirectURI' failed on the 'uri' tag")
|
||||
|
||||
a.RedirectURI = ""
|
||||
err = validate.Struct(a)
|
||||
suite.EqualError(err, "Key: 'Application.RedirectURI' Error:Field validation for 'RedirectURI' failed on the 'required' tag")
|
||||
|
||||
a.RedirectURI = "urn:ietf:wg:oauth:2.0:oob"
|
||||
err = validate.Struct(a)
|
||||
suite.NoError(err)
|
||||
}
|
||||
|
||||
func (suite *ApplicationValidateTestSuite) TestValidateApplicationClientSecret() {
|
||||
a := happyApplication()
|
||||
|
||||
a.ClientSecret = "invalid-uuid"
|
||||
err := validate.Struct(a)
|
||||
suite.EqualError(err, "Key: 'Application.ClientSecret' Error:Field validation for 'ClientSecret' failed on the 'uuid' tag")
|
||||
|
||||
a.ClientSecret = ""
|
||||
err = validate.Struct(a)
|
||||
suite.EqualError(err, "Key: 'Application.ClientSecret' Error:Field validation for 'ClientSecret' failed on the 'required' tag")
|
||||
}
|
||||
|
||||
func (suite *ApplicationValidateTestSuite) TestValidateApplicationScopes() {
|
||||
a := happyApplication()
|
||||
|
||||
a.Scopes = ""
|
||||
err := validate.Struct(a)
|
||||
suite.EqualError(err, "Key: 'Application.Scopes' Error:Field validation for 'Scopes' failed on the 'required' tag")
|
||||
}
|
||||
|
||||
func TestApplicationValidateTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(ApplicationValidateTestSuite))
|
||||
}
|
|
@ -1,115 +0,0 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// 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 validate_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/validate"
|
||||
)
|
||||
|
||||
func happyBlock() *gtsmodel.Block {
|
||||
return >smodel.Block{
|
||||
ID: "01FE91RJR88PSEEE30EV35QR8N",
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
URI: "https://example.org/accounts/someone/blocks/01FE91RJR88PSEEE30EV35QR8N",
|
||||
AccountID: "01FEED79PRMVWPRMFHFQM8MJQN",
|
||||
Account: nil,
|
||||
TargetAccountID: "01FEEDMF6C0QD589MRK7919Z0R",
|
||||
TargetAccount: nil,
|
||||
}
|
||||
}
|
||||
|
||||
type BlockValidateTestSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func (suite *BlockValidateTestSuite) TestValidateBlockHappyPath() {
|
||||
// no problem here
|
||||
b := happyBlock()
|
||||
err := validate.Struct(b)
|
||||
suite.NoError(err)
|
||||
}
|
||||
|
||||
func (suite *BlockValidateTestSuite) TestValidateBlockBadID() {
|
||||
b := happyBlock()
|
||||
|
||||
b.ID = ""
|
||||
err := validate.Struct(b)
|
||||
suite.EqualError(err, "Key: 'Block.ID' Error:Field validation for 'ID' failed on the 'required' tag")
|
||||
|
||||
b.ID = "01FE96W293ZPRG9FQQP48HK8N001FE96W32AT24VYBGM12WN3GKB"
|
||||
err = validate.Struct(b)
|
||||
suite.EqualError(err, "Key: 'Block.ID' Error:Field validation for 'ID' failed on the 'ulid' tag")
|
||||
}
|
||||
|
||||
func (suite *BlockValidateTestSuite) TestValidateBlockNoCreatedAt() {
|
||||
b := happyBlock()
|
||||
|
||||
b.CreatedAt = time.Time{}
|
||||
err := validate.Struct(b)
|
||||
suite.NoError(err)
|
||||
}
|
||||
|
||||
func (suite *BlockValidateTestSuite) TestValidateBlockCreatedByAccountID() {
|
||||
b := happyBlock()
|
||||
|
||||
b.AccountID = ""
|
||||
err := validate.Struct(b)
|
||||
suite.EqualError(err, "Key: 'Block.AccountID' Error:Field validation for 'AccountID' failed on the 'required' tag")
|
||||
|
||||
b.AccountID = "this-is-not-a-valid-ulid"
|
||||
err = validate.Struct(b)
|
||||
suite.EqualError(err, "Key: 'Block.AccountID' Error:Field validation for 'AccountID' failed on the 'ulid' tag")
|
||||
}
|
||||
|
||||
func (suite *BlockValidateTestSuite) TestValidateBlockTargetAccountID() {
|
||||
b := happyBlock()
|
||||
|
||||
b.TargetAccountID = "invalid-ulid"
|
||||
err := validate.Struct(b)
|
||||
suite.EqualError(err, "Key: 'Block.TargetAccountID' Error:Field validation for 'TargetAccountID' failed on the 'ulid' tag")
|
||||
|
||||
b.TargetAccountID = "01FEEDHX4G7EGHF5GD9E82Y51Q"
|
||||
err = validate.Struct(b)
|
||||
suite.NoError(err)
|
||||
|
||||
b.TargetAccountID = ""
|
||||
err = validate.Struct(b)
|
||||
suite.EqualError(err, "Key: 'Block.TargetAccountID' Error:Field validation for 'TargetAccountID' failed on the 'required' tag")
|
||||
}
|
||||
|
||||
func (suite *BlockValidateTestSuite) TestValidateBlockURI() {
|
||||
b := happyBlock()
|
||||
|
||||
b.URI = "invalid-uri"
|
||||
err := validate.Struct(b)
|
||||
suite.EqualError(err, "Key: 'Block.URI' Error:Field validation for 'URI' failed on the 'url' tag")
|
||||
|
||||
b.URI = ""
|
||||
err = validate.Struct(b)
|
||||
suite.EqualError(err, "Key: 'Block.URI' Error:Field validation for 'URI' failed on the 'required' tag")
|
||||
}
|
||||
|
||||
func TestBlockValidateTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(BlockValidateTestSuite))
|
||||
}
|
|
@ -1,101 +0,0 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// 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 validate_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/validate"
|
||||
)
|
||||
|
||||
func happyClient() *gtsmodel.Client {
|
||||
return >smodel.Client{
|
||||
ID: "01FE91RJR88PSEEE30EV35QR8N",
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
Secret: "bd740cf1-024a-4e4d-8c39-866538f52fe6",
|
||||
Domain: "oauth2redirect://com.keylesspalace.tusky/",
|
||||
UserID: "01FEEDMF6C0QD589MRK7919Z0R",
|
||||
}
|
||||
}
|
||||
|
||||
type ClientValidateTestSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func (suite *ClientValidateTestSuite) TestValidateClientHappyPath() {
|
||||
// no problem here
|
||||
c := happyClient()
|
||||
err := validate.Struct(c)
|
||||
suite.NoError(err)
|
||||
}
|
||||
|
||||
func (suite *ClientValidateTestSuite) TestValidateClientBadID() {
|
||||
c := happyClient()
|
||||
|
||||
c.ID = ""
|
||||
err := validate.Struct(c)
|
||||
suite.EqualError(err, "Key: 'Client.ID' Error:Field validation for 'ID' failed on the 'required' tag")
|
||||
|
||||
c.ID = "01FE96W293ZPRG9FQQP48HK8N001FE96W32AT24VYBGM12WN3GKB"
|
||||
err = validate.Struct(c)
|
||||
suite.EqualError(err, "Key: 'Client.ID' Error:Field validation for 'ID' failed on the 'ulid' tag")
|
||||
}
|
||||
|
||||
func (suite *ClientValidateTestSuite) TestValidateClientNoCreatedAt() {
|
||||
c := happyClient()
|
||||
|
||||
c.CreatedAt = time.Time{}
|
||||
err := validate.Struct(c)
|
||||
suite.NoError(err)
|
||||
}
|
||||
|
||||
func (suite *ClientValidateTestSuite) TestValidateClientDomain() {
|
||||
c := happyClient()
|
||||
|
||||
c.Domain = "invalid-uri"
|
||||
err := validate.Struct(c)
|
||||
suite.EqualError(err, "Key: 'Client.Domain' Error:Field validation for 'Domain' failed on the 'uri' tag")
|
||||
|
||||
c.Domain = ""
|
||||
err = validate.Struct(c)
|
||||
suite.EqualError(err, "Key: 'Client.Domain' Error:Field validation for 'Domain' failed on the 'required' tag")
|
||||
|
||||
c.Domain = "urn:ietf:wg:oauth:2.0:oob"
|
||||
err = validate.Struct(c)
|
||||
suite.NoError(err)
|
||||
}
|
||||
|
||||
func (suite *ClientValidateTestSuite) TestValidateSecret() {
|
||||
c := happyClient()
|
||||
|
||||
c.Secret = "invalid-uuid"
|
||||
err := validate.Struct(c)
|
||||
suite.EqualError(err, "Key: 'Client.Secret' Error:Field validation for 'Secret' failed on the 'uuid' tag")
|
||||
|
||||
c.Secret = ""
|
||||
err = validate.Struct(c)
|
||||
suite.EqualError(err, "Key: 'Client.Secret' Error:Field validation for 'Secret' failed on the 'required' tag")
|
||||
}
|
||||
|
||||
func TestClientValidateTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(ClientValidateTestSuite))
|
||||
}
|
|
@ -1,122 +0,0 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// 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 validate_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/validate"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
)
|
||||
|
||||
func happyDomainBlock() *gtsmodel.DomainBlock {
|
||||
return >smodel.DomainBlock{
|
||||
ID: "01FE91RJR88PSEEE30EV35QR8N",
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
Domain: "baddudes.suck",
|
||||
CreatedByAccountID: "01FEED79PRMVWPRMFHFQM8MJQN",
|
||||
PrivateComment: "we don't like em",
|
||||
PublicComment: "poo poo dudes",
|
||||
Obfuscate: testrig.FalseBool(),
|
||||
SubscriptionID: "",
|
||||
}
|
||||
}
|
||||
|
||||
type DomainBlockValidateTestSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func (suite *DomainBlockValidateTestSuite) TestValidateDomainBlockHappyPath() {
|
||||
// no problem here
|
||||
d := happyDomainBlock()
|
||||
err := validate.Struct(d)
|
||||
suite.NoError(err)
|
||||
}
|
||||
|
||||
func (suite *DomainBlockValidateTestSuite) TestValidateDomainBlockBadID() {
|
||||
d := happyDomainBlock()
|
||||
|
||||
d.ID = ""
|
||||
err := validate.Struct(d)
|
||||
suite.EqualError(err, "Key: 'DomainBlock.ID' Error:Field validation for 'ID' failed on the 'required' tag")
|
||||
|
||||
d.ID = "01FE96W293ZPRG9FQQP48HK8N001FE96W32AT24VYBGM12WN3GKB"
|
||||
err = validate.Struct(d)
|
||||
suite.EqualError(err, "Key: 'DomainBlock.ID' Error:Field validation for 'ID' failed on the 'ulid' tag")
|
||||
}
|
||||
|
||||
func (suite *DomainBlockValidateTestSuite) TestValidateDomainBlockNoCreatedAt() {
|
||||
d := happyDomainBlock()
|
||||
|
||||
d.CreatedAt = time.Time{}
|
||||
err := validate.Struct(d)
|
||||
suite.NoError(err)
|
||||
}
|
||||
|
||||
func (suite *DomainBlockValidateTestSuite) TestValidateDomainBlockBadDomain() {
|
||||
d := happyDomainBlock()
|
||||
|
||||
d.Domain = ""
|
||||
err := validate.Struct(d)
|
||||
suite.EqualError(err, "Key: 'DomainBlock.Domain' Error:Field validation for 'Domain' failed on the 'required' tag")
|
||||
|
||||
d.Domain = "this-is-not-a-valid-domain"
|
||||
err = validate.Struct(d)
|
||||
suite.EqualError(err, "Key: 'DomainBlock.Domain' Error:Field validation for 'Domain' failed on the 'fqdn' tag")
|
||||
}
|
||||
|
||||
func (suite *DomainBlockValidateTestSuite) TestValidateDomainBlockCreatedByAccountID() {
|
||||
d := happyDomainBlock()
|
||||
|
||||
d.CreatedByAccountID = ""
|
||||
err := validate.Struct(d)
|
||||
suite.EqualError(err, "Key: 'DomainBlock.CreatedByAccountID' Error:Field validation for 'CreatedByAccountID' failed on the 'required' tag")
|
||||
|
||||
d.CreatedByAccountID = "this-is-not-a-valid-ulid"
|
||||
err = validate.Struct(d)
|
||||
suite.EqualError(err, "Key: 'DomainBlock.CreatedByAccountID' Error:Field validation for 'CreatedByAccountID' failed on the 'ulid' tag")
|
||||
}
|
||||
|
||||
func (suite *DomainBlockValidateTestSuite) TestValidateDomainBlockComments() {
|
||||
d := happyDomainBlock()
|
||||
|
||||
d.PrivateComment = ""
|
||||
d.PublicComment = ""
|
||||
err := validate.Struct(d)
|
||||
suite.NoError(err)
|
||||
}
|
||||
|
||||
func (suite *DomainBlockValidateTestSuite) TestValidateDomainSubscriptionID() {
|
||||
d := happyDomainBlock()
|
||||
|
||||
d.SubscriptionID = "invalid-ulid"
|
||||
err := validate.Struct(d)
|
||||
suite.EqualError(err, "Key: 'DomainBlock.SubscriptionID' Error:Field validation for 'SubscriptionID' failed on the 'ulid' tag")
|
||||
|
||||
d.SubscriptionID = "01FEEDHX4G7EGHF5GD9E82Y51Q"
|
||||
err = validate.Struct(d)
|
||||
suite.NoError(err)
|
||||
}
|
||||
|
||||
func TestDomainBlockValidateTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(DomainBlockValidateTestSuite))
|
||||
}
|
|
@ -1,96 +0,0 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// 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 validate_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/validate"
|
||||
)
|
||||
|
||||
func happyEmailDomainBlock() *gtsmodel.EmailDomainBlock {
|
||||
return >smodel.EmailDomainBlock{
|
||||
ID: "01FE91RJR88PSEEE30EV35QR8N",
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
Domain: "baddudes.suck",
|
||||
CreatedByAccountID: "01FEED79PRMVWPRMFHFQM8MJQN",
|
||||
}
|
||||
}
|
||||
|
||||
type EmailDomainBlockValidateTestSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func (suite *EmailDomainBlockValidateTestSuite) TestValidateEmailDomainBlockHappyPath() {
|
||||
// no problem here
|
||||
e := happyEmailDomainBlock()
|
||||
err := validate.Struct(e)
|
||||
suite.NoError(err)
|
||||
}
|
||||
|
||||
func (suite *EmailDomainBlockValidateTestSuite) TestValidateEmailDomainBlockBadID() {
|
||||
e := happyEmailDomainBlock()
|
||||
|
||||
e.ID = ""
|
||||
err := validate.Struct(e)
|
||||
suite.EqualError(err, "Key: 'EmailDomainBlock.ID' Error:Field validation for 'ID' failed on the 'required' tag")
|
||||
|
||||
e.ID = "01FE96W293ZPRG9FQQP48HK8N001FE96W32AT24VYBGM12WN3GKB"
|
||||
err = validate.Struct(e)
|
||||
suite.EqualError(err, "Key: 'EmailDomainBlock.ID' Error:Field validation for 'ID' failed on the 'ulid' tag")
|
||||
}
|
||||
|
||||
func (suite *EmailDomainBlockValidateTestSuite) TestValidateEmailDomainBlockNoCreatedAt() {
|
||||
e := happyEmailDomainBlock()
|
||||
|
||||
e.CreatedAt = time.Time{}
|
||||
err := validate.Struct(e)
|
||||
suite.NoError(err)
|
||||
}
|
||||
|
||||
func (suite *EmailDomainBlockValidateTestSuite) TestValidateEmailDomainBlockBadDomain() {
|
||||
e := happyEmailDomainBlock()
|
||||
|
||||
e.Domain = ""
|
||||
err := validate.Struct(e)
|
||||
suite.EqualError(err, "Key: 'EmailDomainBlock.Domain' Error:Field validation for 'Domain' failed on the 'required' tag")
|
||||
|
||||
e.Domain = "this-is-not-a-valid-domain"
|
||||
err = validate.Struct(e)
|
||||
suite.EqualError(err, "Key: 'EmailDomainBlock.Domain' Error:Field validation for 'Domain' failed on the 'fqdn' tag")
|
||||
}
|
||||
|
||||
func (suite *EmailDomainBlockValidateTestSuite) TestValidateEmailDomainBlockCreatedByAccountID() {
|
||||
e := happyEmailDomainBlock()
|
||||
|
||||
e.CreatedByAccountID = ""
|
||||
err := validate.Struct(e)
|
||||
suite.EqualError(err, "Key: 'EmailDomainBlock.CreatedByAccountID' Error:Field validation for 'CreatedByAccountID' failed on the 'required' tag")
|
||||
|
||||
e.CreatedByAccountID = "this-is-not-a-valid-ulid"
|
||||
err = validate.Struct(e)
|
||||
suite.EqualError(err, "Key: 'EmailDomainBlock.CreatedByAccountID' Error:Field validation for 'CreatedByAccountID' failed on the 'ulid' tag")
|
||||
}
|
||||
|
||||
func TestEmailDomainBlockValidateTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(EmailDomainBlockValidateTestSuite))
|
||||
}
|
|
@ -1,195 +0,0 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// 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 validate_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/validate"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
)
|
||||
|
||||
func happyEmoji() *gtsmodel.Emoji {
|
||||
// the file validator actually runs os.Stat on given paths, so we need to just create small
|
||||
// temp files for both the main attachment file and the thumbnail
|
||||
|
||||
imageFile, err := os.CreateTemp("", "gts_test_emoji")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if _, err := imageFile.WriteString("main"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
imagePath := imageFile.Name()
|
||||
if err := imageFile.Close(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
staticFile, err := os.CreateTemp("", "gts_test_emoji_static")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if _, err := staticFile.WriteString("thumbnail"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
imageStaticPath := staticFile.Name()
|
||||
if err := staticFile.Close(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return >smodel.Emoji{
|
||||
ID: "01F8MH6NEM8D7527KZAECTCR76",
|
||||
CreatedAt: time.Now().Add(-71 * time.Hour),
|
||||
UpdatedAt: time.Now().Add(-71 * time.Hour),
|
||||
Shortcode: "blob_test",
|
||||
Domain: "example.org",
|
||||
ImageRemoteURL: "https://example.org/emojis/blob_test.gif",
|
||||
ImageStaticRemoteURL: "https://example.org/emojis/blob_test.png",
|
||||
ImageURL: "",
|
||||
ImageStaticURL: "",
|
||||
ImagePath: imagePath,
|
||||
ImageStaticPath: imageStaticPath,
|
||||
ImageContentType: "image/gif",
|
||||
ImageStaticContentType: "image/png",
|
||||
ImageFileSize: 1024,
|
||||
ImageStaticFileSize: 256,
|
||||
ImageUpdatedAt: time.Now(),
|
||||
Disabled: testrig.FalseBool(),
|
||||
URI: "https://example.org/emojis/blob_test",
|
||||
VisibleInPicker: testrig.TrueBool(),
|
||||
CategoryID: "01FEE47ZH70PWDSEAVBRFNX325",
|
||||
}
|
||||
}
|
||||
|
||||
type EmojiValidateTestSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func (suite *EmojiValidateTestSuite) TestValidateEmojiHappyPath() {
|
||||
// no problem here
|
||||
m := happyEmoji()
|
||||
err := validate.Struct(*m)
|
||||
suite.NoError(err)
|
||||
}
|
||||
|
||||
func (suite *EmojiValidateTestSuite) TestValidateEmojiBadFilePaths() {
|
||||
e := happyEmoji()
|
||||
|
||||
e.ImagePath = "/tmp/nonexistent/file/for/gotosocial/test"
|
||||
err := validate.Struct(e)
|
||||
suite.EqualError(err, "Key: 'Emoji.ImagePath' Error:Field validation for 'ImagePath' failed on the 'file' tag")
|
||||
|
||||
e.ImagePath = ""
|
||||
err = validate.Struct(e)
|
||||
suite.EqualError(err, "Key: 'Emoji.ImagePath' Error:Field validation for 'ImagePath' failed on the 'required' tag")
|
||||
|
||||
e.ImagePath = "???????????thisnot a valid path####"
|
||||
err = validate.Struct(e)
|
||||
suite.EqualError(err, "Key: 'Emoji.ImagePath' Error:Field validation for 'ImagePath' failed on the 'file' tag")
|
||||
|
||||
e.ImageStaticPath = "/tmp/nonexistent/file/for/gotosocial/test"
|
||||
err = validate.Struct(e)
|
||||
suite.EqualError(err, "Key: 'Emoji.ImagePath' Error:Field validation for 'ImagePath' failed on the 'file' tag\nKey: 'Emoji.ImageStaticPath' Error:Field validation for 'ImageStaticPath' failed on the 'file' tag")
|
||||
|
||||
e.ImageStaticPath = ""
|
||||
err = validate.Struct(e)
|
||||
suite.EqualError(err, "Key: 'Emoji.ImagePath' Error:Field validation for 'ImagePath' failed on the 'file' tag\nKey: 'Emoji.ImageStaticPath' Error:Field validation for 'ImageStaticPath' failed on the 'required' tag")
|
||||
|
||||
e.ImageStaticPath = "???????????thisnot a valid path####"
|
||||
err = validate.Struct(e)
|
||||
suite.EqualError(err, "Key: 'Emoji.ImagePath' Error:Field validation for 'ImagePath' failed on the 'file' tag\nKey: 'Emoji.ImageStaticPath' Error:Field validation for 'ImageStaticPath' failed on the 'file' tag")
|
||||
}
|
||||
|
||||
func (suite *EmojiValidateTestSuite) TestValidateEmojiURI() {
|
||||
e := happyEmoji()
|
||||
|
||||
e.URI = "aaaaaaaaaa"
|
||||
err := validate.Struct(e)
|
||||
suite.EqualError(err, "Key: 'Emoji.URI' Error:Field validation for 'URI' failed on the 'url' tag")
|
||||
|
||||
e.URI = ""
|
||||
err = validate.Struct(e)
|
||||
suite.EqualError(err, "Key: 'Emoji.URI' Error:Field validation for 'URI' failed on the 'url' tag")
|
||||
}
|
||||
|
||||
func (suite *EmojiValidateTestSuite) TestValidateEmojiURLCombos() {
|
||||
e := happyEmoji()
|
||||
|
||||
e.ImageRemoteURL = ""
|
||||
err := validate.Struct(e)
|
||||
suite.EqualError(err, "Key: 'Emoji.ImageRemoteURL' Error:Field validation for 'ImageRemoteURL' failed on the 'required_without' tag\nKey: 'Emoji.ImageURL' Error:Field validation for 'ImageURL' failed on the 'required_without' tag")
|
||||
|
||||
e.ImageURL = "https://whatever.org"
|
||||
err = validate.Struct(e)
|
||||
suite.NoError(err)
|
||||
|
||||
e.ImageStaticRemoteURL = ""
|
||||
err = validate.Struct(e)
|
||||
suite.EqualError(err, "Key: 'Emoji.ImageStaticRemoteURL' Error:Field validation for 'ImageStaticRemoteURL' failed on the 'required_without' tag\nKey: 'Emoji.ImageStaticURL' Error:Field validation for 'ImageStaticURL' failed on the 'required_without' tag")
|
||||
|
||||
e.ImageStaticURL = "https://whatever.org"
|
||||
err = validate.Struct(e)
|
||||
suite.NoError(err)
|
||||
|
||||
e.ImageURL = ""
|
||||
e.ImageStaticURL = ""
|
||||
e.ImageRemoteURL = ""
|
||||
e.ImageStaticRemoteURL = ""
|
||||
err = validate.Struct(e)
|
||||
suite.EqualError(err, "Key: 'Emoji.ImageRemoteURL' Error:Field validation for 'ImageRemoteURL' failed on the 'required_without' tag\nKey: 'Emoji.ImageStaticRemoteURL' Error:Field validation for 'ImageStaticRemoteURL' failed on the 'required_without' tag\nKey: 'Emoji.ImageURL' Error:Field validation for 'ImageURL' failed on the 'required_without' tag\nKey: 'Emoji.ImageStaticURL' Error:Field validation for 'ImageStaticURL' failed on the 'required_without' tag")
|
||||
}
|
||||
|
||||
func (suite *EmojiValidateTestSuite) TestValidateFileSize() {
|
||||
e := happyEmoji()
|
||||
|
||||
e.ImageFileSize = 0
|
||||
err := validate.Struct(e)
|
||||
suite.EqualError(err, "Key: 'Emoji.ImageFileSize' Error:Field validation for 'ImageFileSize' failed on the 'required' tag")
|
||||
|
||||
e.ImageStaticFileSize = 0
|
||||
err = validate.Struct(e)
|
||||
suite.EqualError(err, "Key: 'Emoji.ImageFileSize' Error:Field validation for 'ImageFileSize' failed on the 'required' tag\nKey: 'Emoji.ImageStaticFileSize' Error:Field validation for 'ImageStaticFileSize' failed on the 'required' tag")
|
||||
|
||||
e.ImageFileSize = -1
|
||||
err = validate.Struct(e)
|
||||
suite.EqualError(err, "Key: 'Emoji.ImageFileSize' Error:Field validation for 'ImageFileSize' failed on the 'min' tag\nKey: 'Emoji.ImageStaticFileSize' Error:Field validation for 'ImageStaticFileSize' failed on the 'required' tag")
|
||||
|
||||
e.ImageStaticFileSize = -1
|
||||
err = validate.Struct(e)
|
||||
suite.EqualError(err, "Key: 'Emoji.ImageFileSize' Error:Field validation for 'ImageFileSize' failed on the 'min' tag\nKey: 'Emoji.ImageStaticFileSize' Error:Field validation for 'ImageStaticFileSize' failed on the 'min' tag")
|
||||
}
|
||||
|
||||
func (suite *EmojiValidateTestSuite) TestValidateDomain() {
|
||||
e := happyEmoji()
|
||||
|
||||
e.Domain = ""
|
||||
err := validate.Struct(e)
|
||||
suite.EqualError(err, "Key: 'Emoji.ImageURL' Error:Field validation for 'ImageURL' failed on the 'required_without' tag\nKey: 'Emoji.ImageStaticURL' Error:Field validation for 'ImageStaticURL' failed on the 'required_without' tag")
|
||||
|
||||
e.Domain = "aaaaaaaaa"
|
||||
err = validate.Struct(e)
|
||||
suite.EqualError(err, "Key: 'Emoji.Domain' Error:Field validation for 'Domain' failed on the 'fqdn' tag")
|
||||
}
|
||||
|
||||
func TestEmojiValidateTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(EmojiValidateTestSuite))
|
||||
}
|
|
@ -1,87 +0,0 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// 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 validate_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/validate"
|
||||
)
|
||||
|
||||
func happyFollow() *gtsmodel.Follow {
|
||||
return >smodel.Follow{
|
||||
ID: "01FE91RJR88PSEEE30EV35QR8N",
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
AccountID: "01FE96MAE58MXCE5C4SSMEMCEK",
|
||||
Account: nil,
|
||||
TargetAccountID: "01FE96MXRHWZHKC0WH5FT82H1A",
|
||||
TargetAccount: nil,
|
||||
URI: "https://example.org/users/user1/activity/follow/01FE91RJR88PSEEE30EV35QR8N",
|
||||
}
|
||||
}
|
||||
|
||||
type FollowValidateTestSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func (suite *FollowValidateTestSuite) TestValidateFollowHappyPath() {
|
||||
// no problem here
|
||||
f := happyFollow()
|
||||
err := validate.Struct(f)
|
||||
suite.NoError(err)
|
||||
}
|
||||
|
||||
func (suite *FollowValidateTestSuite) TestValidateFollowBadID() {
|
||||
f := happyFollow()
|
||||
|
||||
f.ID = ""
|
||||
err := validate.Struct(f)
|
||||
suite.EqualError(err, "Key: 'Follow.ID' Error:Field validation for 'ID' failed on the 'required' tag")
|
||||
|
||||
f.ID = "01FE96W293ZPRG9FQQP48HK8N001FE96W32AT24VYBGM12WN3GKB"
|
||||
err = validate.Struct(f)
|
||||
suite.EqualError(err, "Key: 'Follow.ID' Error:Field validation for 'ID' failed on the 'ulid' tag")
|
||||
}
|
||||
|
||||
func (suite *FollowValidateTestSuite) TestValidateFollowNoCreatedAt() {
|
||||
f := happyFollow()
|
||||
|
||||
f.CreatedAt = time.Time{}
|
||||
err := validate.Struct(f)
|
||||
suite.NoError(err)
|
||||
}
|
||||
|
||||
func (suite *FollowValidateTestSuite) TestValidateFollowNoURI() {
|
||||
f := happyFollow()
|
||||
|
||||
f.URI = ""
|
||||
err := validate.Struct(f)
|
||||
suite.EqualError(err, "Key: 'Follow.URI' Error:Field validation for 'URI' failed on the 'required' tag")
|
||||
|
||||
f.URI = "this-is-not-a-valid-url"
|
||||
err = validate.Struct(f)
|
||||
suite.EqualError(err, "Key: 'Follow.URI' Error:Field validation for 'URI' failed on the 'url' tag")
|
||||
}
|
||||
|
||||
func TestFollowValidateTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(FollowValidateTestSuite))
|
||||
}
|
|
@ -1,87 +0,0 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// 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 validate_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/validate"
|
||||
)
|
||||
|
||||
func happyFollowRequest() *gtsmodel.FollowRequest {
|
||||
return >smodel.FollowRequest{
|
||||
ID: "01FE91RJR88PSEEE30EV35QR8N",
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
AccountID: "01FE96MAE58MXCE5C4SSMEMCEK",
|
||||
Account: nil,
|
||||
TargetAccountID: "01FE96MXRHWZHKC0WH5FT82H1A",
|
||||
TargetAccount: nil,
|
||||
URI: "https://example.org/users/user1/activity/follow/01FE91RJR88PSEEE30EV35QR8N",
|
||||
}
|
||||
}
|
||||
|
||||
type FollowRequestValidateTestSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func (suite *FollowRequestValidateTestSuite) TestValidateFollowRequestHappyPath() {
|
||||
// no problem here
|
||||
f := happyFollowRequest()
|
||||
err := validate.Struct(f)
|
||||
suite.NoError(err)
|
||||
}
|
||||
|
||||
func (suite *FollowRequestValidateTestSuite) TestValidateFollowRequestBadID() {
|
||||
f := happyFollowRequest()
|
||||
|
||||
f.ID = ""
|
||||
err := validate.Struct(f)
|
||||
suite.EqualError(err, "Key: 'FollowRequest.ID' Error:Field validation for 'ID' failed on the 'required' tag")
|
||||
|
||||
f.ID = "01FE96W293ZPRG9FQQP48HK8N001FE96W32AT24VYBGM12WN3GKB"
|
||||
err = validate.Struct(f)
|
||||
suite.EqualError(err, "Key: 'FollowRequest.ID' Error:Field validation for 'ID' failed on the 'ulid' tag")
|
||||
}
|
||||
|
||||
func (suite *FollowRequestValidateTestSuite) TestValidateFollowRequestNoCreatedAt() {
|
||||
f := happyFollowRequest()
|
||||
|
||||
f.CreatedAt = time.Time{}
|
||||
err := validate.Struct(f)
|
||||
suite.NoError(err)
|
||||
}
|
||||
|
||||
func (suite *FollowRequestValidateTestSuite) TestValidateFollowRequestNoURI() {
|
||||
f := happyFollowRequest()
|
||||
|
||||
f.URI = ""
|
||||
err := validate.Struct(f)
|
||||
suite.EqualError(err, "Key: 'FollowRequest.URI' Error:Field validation for 'URI' failed on the 'required' tag")
|
||||
|
||||
f.URI = "this-is-not-a-valid-url"
|
||||
err = validate.Struct(f)
|
||||
suite.EqualError(err, "Key: 'FollowRequest.URI' Error:Field validation for 'URI' failed on the 'url' tag")
|
||||
}
|
||||
|
||||
func TestFollowRequestValidateTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(FollowRequestValidateTestSuite))
|
||||
}
|
|
@ -99,14 +99,19 @@ func Email(email string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
// Language checks that the given language string is a 2- or 3-letter ISO 639 code.
|
||||
// Returns an error if the language cannot be parsed. See: https://pkg.go.dev/golang.org/x/text/language
|
||||
func Language(lang string) error {
|
||||
// Language checks that the given language string is a valid, if not necessarily canonical, BCP 47 language tag.
|
||||
// Returns a canonicalized version of the tag if the language can be parsed.
|
||||
// Returns an error if the language cannot be parsed.
|
||||
// See: https://pkg.go.dev/golang.org/x/text/language
|
||||
func Language(lang string) (string, error) {
|
||||
if lang == "" {
|
||||
return errors.New("no language provided")
|
||||
return "", errors.New("no language provided")
|
||||
}
|
||||
_, err := language.ParseBase(lang)
|
||||
return err
|
||||
parsed, err := language.Parse(lang)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return parsed.String(), err
|
||||
}
|
||||
|
||||
// SignUpReason checks that a sufficient reason is given for a server signup request
|
||||
|
|
|
@ -159,60 +159,39 @@ func (suite *ValidationTestSuite) TestValidateEmail() {
|
|||
}
|
||||
|
||||
func (suite *ValidationTestSuite) TestValidateLanguage() {
|
||||
empty := ""
|
||||
notALanguage := "this isn't a language at all!"
|
||||
english := "en"
|
||||
capitalEnglish := "EN"
|
||||
arabic3Letters := "ara"
|
||||
mixedCapsEnglish := "eN"
|
||||
englishUS := "en-us"
|
||||
dutch := "nl"
|
||||
german := "de"
|
||||
var err error
|
||||
|
||||
err = validate.Language(empty)
|
||||
if suite.Error(err) {
|
||||
suite.Equal(errors.New("no language provided"), err)
|
||||
testCases := []struct {
|
||||
name, input, expected, err string
|
||||
}{
|
||||
{name: "empty", err: "no language provided"},
|
||||
{name: "notALanguage", input: "this isn't a language at all!", err: "language: tag is not well-formed"},
|
||||
{name: "english", input: "en", expected: "en"},
|
||||
// Should be all lowercase
|
||||
{name: "capitalEnglish", input: "EN", expected: "en"},
|
||||
// Overlong, should be in ISO 639-1 format
|
||||
{name: "arabic3Letters", input: "ara", expected: "ar"},
|
||||
// Should be all lowercase
|
||||
{name: "mixedCapsEnglish", input: "eN", expected: "en"},
|
||||
// Region should be capitalized
|
||||
{name: "englishUS", input: "en-us", expected: "en-US"},
|
||||
{name: "dutch", input: "nl", expected: "nl"},
|
||||
{name: "german", input: "de", expected: "de"},
|
||||
{name: "chinese", input: "zh", expected: "zh"},
|
||||
{name: "chineseSimplified", input: "zh-Hans", expected: "zh-Hans"},
|
||||
{name: "chineseTraditional", input: "zh-Hant", expected: "zh-Hant"},
|
||||
}
|
||||
|
||||
err = validate.Language(notALanguage)
|
||||
if suite.Error(err) {
|
||||
suite.Equal(errors.New("language: tag is not well-formed"), err)
|
||||
}
|
||||
|
||||
err = validate.Language(english)
|
||||
if suite.NoError(err) {
|
||||
suite.Equal(nil, err)
|
||||
}
|
||||
|
||||
err = validate.Language(capitalEnglish)
|
||||
if suite.NoError(err) {
|
||||
suite.Equal(nil, err)
|
||||
}
|
||||
|
||||
err = validate.Language(arabic3Letters)
|
||||
if suite.NoError(err) {
|
||||
suite.Equal(nil, err)
|
||||
}
|
||||
|
||||
err = validate.Language(mixedCapsEnglish)
|
||||
if suite.NoError(err) {
|
||||
suite.Equal(nil, err)
|
||||
}
|
||||
|
||||
err = validate.Language(englishUS)
|
||||
if suite.Error(err) {
|
||||
suite.Equal(errors.New("language: tag is not well-formed"), err)
|
||||
}
|
||||
|
||||
err = validate.Language(dutch)
|
||||
if suite.NoError(err) {
|
||||
suite.Equal(nil, err)
|
||||
}
|
||||
|
||||
err = validate.Language(german)
|
||||
if suite.NoError(err) {
|
||||
suite.Equal(nil, err)
|
||||
for _, testCase := range testCases {
|
||||
testCase := testCase
|
||||
suite.Run(testCase.name, func() {
|
||||
actual, actualErr := validate.Language(testCase.input)
|
||||
if testCase.err == "" {
|
||||
suite.Equal(testCase.expected, actual)
|
||||
suite.NoError(actualErr)
|
||||
} else {
|
||||
suite.Empty(actual)
|
||||
suite.EqualError(actualErr, testCase.err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,145 +0,0 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// 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 validate_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/validate"
|
||||
)
|
||||
|
||||
func happyInstance() *gtsmodel.Instance {
|
||||
return >smodel.Instance{
|
||||
ID: "01FE91RJR88PSEEE30EV35QR8N",
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
Domain: "example.org",
|
||||
Title: "Example Instance",
|
||||
URI: "https://example.org",
|
||||
SuspendedAt: time.Time{},
|
||||
DomainBlockID: "",
|
||||
DomainBlock: nil,
|
||||
ShortDescription: "This is a description for the example/testing instance.",
|
||||
Description: "This is a way longer description for the example/testing instance!",
|
||||
Terms: "Don't be a knobhead.",
|
||||
ContactEmail: "admin@example.org",
|
||||
ContactAccountUsername: "admin",
|
||||
ContactAccountID: "01FEE20H5QWHJDEXAEE9G96PR0",
|
||||
ContactAccount: nil,
|
||||
Reputation: 420,
|
||||
Version: "gotosocial 0.1.0",
|
||||
}
|
||||
}
|
||||
|
||||
type InstanceValidateTestSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func (suite *InstanceValidateTestSuite) TestValidateInstanceHappyPath() {
|
||||
// no problem here
|
||||
m := happyInstance()
|
||||
err := validate.Struct(*m)
|
||||
suite.NoError(err)
|
||||
}
|
||||
|
||||
func (suite *InstanceValidateTestSuite) TestValidateInstanceBadID() {
|
||||
m := happyInstance()
|
||||
|
||||
m.ID = ""
|
||||
err := validate.Struct(*m)
|
||||
suite.EqualError(err, "Key: 'Instance.ID' Error:Field validation for 'ID' failed on the 'required' tag")
|
||||
|
||||
m.ID = "01FE96W293ZPRG9FQQP48HK8N001FE96W32AT24VYBGM12WN3GKB"
|
||||
err = validate.Struct(*m)
|
||||
suite.EqualError(err, "Key: 'Instance.ID' Error:Field validation for 'ID' failed on the 'ulid' tag")
|
||||
}
|
||||
|
||||
func (suite *InstanceValidateTestSuite) TestValidateInstanceAccountURI() {
|
||||
i := happyInstance()
|
||||
|
||||
i.URI = ""
|
||||
err := validate.Struct(i)
|
||||
suite.EqualError(err, "Key: 'Instance.URI' Error:Field validation for 'URI' failed on the 'required' tag")
|
||||
|
||||
i.URI = "---------------------------"
|
||||
err = validate.Struct(i)
|
||||
suite.EqualError(err, "Key: 'Instance.URI' Error:Field validation for 'URI' failed on the 'url' tag")
|
||||
}
|
||||
|
||||
func (suite *InstanceValidateTestSuite) TestValidateInstanceDodgyAccountID() {
|
||||
i := happyInstance()
|
||||
|
||||
i.ContactAccountID = "9HZJ76B6VXSKF"
|
||||
err := validate.Struct(i)
|
||||
suite.EqualError(err, "Key: 'Instance.ContactAccountID' Error:Field validation for 'ContactAccountID' failed on the 'ulid' tag")
|
||||
|
||||
i.ContactAccountID = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!!!!!!!!!!!!"
|
||||
err = validate.Struct(i)
|
||||
suite.EqualError(err, "Key: 'Instance.ContactAccountID' Error:Field validation for 'ContactAccountID' failed on the 'ulid' tag")
|
||||
|
||||
i.ContactAccountID = ""
|
||||
err = validate.Struct(i)
|
||||
suite.EqualError(err, "Key: 'Instance.ContactAccountID' Error:Field validation for 'ContactAccountID' failed on the 'required_with' tag")
|
||||
|
||||
i.ContactAccountUsername = ""
|
||||
err = validate.Struct(i)
|
||||
suite.NoError(err)
|
||||
}
|
||||
|
||||
func (suite *InstanceValidateTestSuite) TestValidateInstanceDomain() {
|
||||
i := happyInstance()
|
||||
|
||||
i.Domain = "poopoo"
|
||||
err := validate.Struct(i)
|
||||
suite.EqualError(err, "Key: 'Instance.Domain' Error:Field validation for 'Domain' failed on the 'fqdn' tag")
|
||||
|
||||
i.Domain = ""
|
||||
err = validate.Struct(i)
|
||||
suite.EqualError(err, "Key: 'Instance.Domain' Error:Field validation for 'Domain' failed on the 'required' tag")
|
||||
|
||||
i.Domain = "https://aaaaaaaaaaaaah.org"
|
||||
err = validate.Struct(i)
|
||||
suite.EqualError(err, "Key: 'Instance.Domain' Error:Field validation for 'Domain' failed on the 'fqdn' tag")
|
||||
}
|
||||
|
||||
func (suite *InstanceValidateTestSuite) TestValidateInstanceContactEmail() {
|
||||
i := happyInstance()
|
||||
|
||||
i.ContactEmail = "poopoo"
|
||||
err := validate.Struct(i)
|
||||
suite.EqualError(err, "Key: 'Instance.ContactEmail' Error:Field validation for 'ContactEmail' failed on the 'email' tag")
|
||||
|
||||
i.ContactEmail = ""
|
||||
err = validate.Struct(i)
|
||||
suite.NoError(err)
|
||||
}
|
||||
|
||||
func (suite *InstanceValidateTestSuite) TestValidateInstanceNoCreatedAt() {
|
||||
i := happyInstance()
|
||||
|
||||
i.CreatedAt = time.Time{}
|
||||
err := validate.Struct(i)
|
||||
suite.NoError(err)
|
||||
}
|
||||
|
||||
func TestInstanceValidateTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(InstanceValidateTestSuite))
|
||||
}
|
|
@ -1,230 +0,0 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// 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 validate_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/validate"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
)
|
||||
|
||||
func happyMediaAttachment() *gtsmodel.MediaAttachment {
|
||||
// the file validator actually runs os.Stat on given paths, so we need to just create small
|
||||
// temp files for both the main attachment file and the thumbnail
|
||||
|
||||
mainFile, err := os.CreateTemp("", "gts_test_mainfile")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if _, err := mainFile.WriteString("main"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
mainPath := mainFile.Name()
|
||||
if err := mainFile.Close(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
thumbnailFile, err := os.CreateTemp("", "gts_test_thumbnail")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if _, err := thumbnailFile.WriteString("thumbnail"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
thumbnailPath := thumbnailFile.Name()
|
||||
if err := thumbnailFile.Close(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return >smodel.MediaAttachment{
|
||||
ID: "01F8MH6NEM8D7527KZAECTCR76",
|
||||
CreatedAt: time.Now().Add(-71 * time.Hour),
|
||||
UpdatedAt: time.Now().Add(-71 * time.Hour),
|
||||
StatusID: "01F8MH75CBF9JFX4ZAD54N0W0R",
|
||||
URL: "http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/attachment/original/01F8MH6NEM8D7527KZAECTCR76.jpg",
|
||||
RemoteURL: "",
|
||||
Type: gtsmodel.FileTypeImage,
|
||||
FileMeta: gtsmodel.FileMeta{
|
||||
Original: gtsmodel.Original{
|
||||
Width: 1200,
|
||||
Height: 630,
|
||||
Size: 756000,
|
||||
Aspect: 1.9047619047619047,
|
||||
},
|
||||
Small: gtsmodel.Small{
|
||||
Width: 256,
|
||||
Height: 134,
|
||||
Size: 34304,
|
||||
Aspect: 1.9104477611940298,
|
||||
},
|
||||
},
|
||||
AccountID: "01F8MH17FWEB39HZJ76B6VXSKF",
|
||||
Description: "Black and white image of some 50's style text saying: Welcome On Board",
|
||||
ScheduledStatusID: "",
|
||||
Blurhash: "LNJRdVM{00Rj%Mayt7j[4nWBofRj",
|
||||
Processing: 2,
|
||||
File: gtsmodel.File{
|
||||
Path: mainPath,
|
||||
ContentType: "image/jpeg",
|
||||
FileSize: 62529,
|
||||
UpdatedAt: time.Now().Add(-71 * time.Hour),
|
||||
},
|
||||
Thumbnail: gtsmodel.Thumbnail{
|
||||
Path: thumbnailPath,
|
||||
ContentType: "image/jpeg",
|
||||
FileSize: 6872,
|
||||
UpdatedAt: time.Now().Add(-71 * time.Hour),
|
||||
URL: "http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/attachment/small/01F8MH6NEM8D7527KZAECTCR76.jpg",
|
||||
RemoteURL: "",
|
||||
},
|
||||
Avatar: testrig.FalseBool(),
|
||||
Header: testrig.FalseBool(),
|
||||
}
|
||||
}
|
||||
|
||||
type MediaAttachmentValidateTestSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func (suite *MediaAttachmentValidateTestSuite) TestValidateMediaAttachmentHappyPath() {
|
||||
// no problem here
|
||||
m := happyMediaAttachment()
|
||||
err := validate.Struct(m)
|
||||
suite.NoError(err)
|
||||
}
|
||||
|
||||
func (suite *MediaAttachmentValidateTestSuite) TestValidateMediaAttachmentBadFilePaths() {
|
||||
m := happyMediaAttachment()
|
||||
|
||||
m.File.Path = "/tmp/nonexistent/file/for/gotosocial/test"
|
||||
err := validate.Struct(m)
|
||||
suite.EqualError(err, "Key: 'MediaAttachment.File.Path' Error:Field validation for 'Path' failed on the 'file' tag")
|
||||
|
||||
m.File.Path = ""
|
||||
err = validate.Struct(m)
|
||||
suite.EqualError(err, "Key: 'MediaAttachment.File.Path' Error:Field validation for 'Path' failed on the 'required' tag")
|
||||
|
||||
m.File.Path = "???????????thisnot a valid path####"
|
||||
err = validate.Struct(m)
|
||||
suite.EqualError(err, "Key: 'MediaAttachment.File.Path' Error:Field validation for 'Path' failed on the 'file' tag")
|
||||
|
||||
m.Thumbnail.Path = "/tmp/nonexistent/file/for/gotosocial/test"
|
||||
err = validate.Struct(m)
|
||||
suite.EqualError(err, "Key: 'MediaAttachment.File.Path' Error:Field validation for 'Path' failed on the 'file' tag\nKey: 'MediaAttachment.Thumbnail.Path' Error:Field validation for 'Path' failed on the 'file' tag")
|
||||
|
||||
m.Thumbnail.Path = ""
|
||||
err = validate.Struct(m)
|
||||
suite.EqualError(err, "Key: 'MediaAttachment.File.Path' Error:Field validation for 'Path' failed on the 'file' tag\nKey: 'MediaAttachment.Thumbnail.Path' Error:Field validation for 'Path' failed on the 'required' tag")
|
||||
|
||||
m.Thumbnail.Path = "???????????thisnot a valid path####"
|
||||
err = validate.Struct(m)
|
||||
suite.EqualError(err, "Key: 'MediaAttachment.File.Path' Error:Field validation for 'Path' failed on the 'file' tag\nKey: 'MediaAttachment.Thumbnail.Path' Error:Field validation for 'Path' failed on the 'file' tag")
|
||||
}
|
||||
|
||||
func (suite *MediaAttachmentValidateTestSuite) TestValidateMediaAttachmentBadType() {
|
||||
m := happyMediaAttachment()
|
||||
|
||||
m.Type = ""
|
||||
err := validate.Struct(m)
|
||||
suite.EqualError(err, "Key: 'MediaAttachment.Type' Error:Field validation for 'Type' failed on the 'oneof' tag")
|
||||
|
||||
m.Type = "Not Supported"
|
||||
err = validate.Struct(m)
|
||||
suite.EqualError(err, "Key: 'MediaAttachment.Type' Error:Field validation for 'Type' failed on the 'oneof' tag")
|
||||
}
|
||||
|
||||
func (suite *MediaAttachmentValidateTestSuite) TestValidateMediaAttachmentBadFileMeta() {
|
||||
m := happyMediaAttachment()
|
||||
|
||||
m.FileMeta.Original.Aspect = 0
|
||||
err := validate.Struct(m)
|
||||
suite.EqualError(err, "Key: 'MediaAttachment.FileMeta.Original.Aspect' Error:Field validation for 'Aspect' failed on the 'required_with' tag")
|
||||
|
||||
m.FileMeta.Original.Height = 0
|
||||
err = validate.Struct(m)
|
||||
suite.EqualError(err, "Key: 'MediaAttachment.FileMeta.Original.Height' Error:Field validation for 'Height' failed on the 'required_with' tag\nKey: 'MediaAttachment.FileMeta.Original.Aspect' Error:Field validation for 'Aspect' failed on the 'required_with' tag")
|
||||
|
||||
m.FileMeta.Original = gtsmodel.Original{}
|
||||
err = validate.Struct(m)
|
||||
suite.NoError(err)
|
||||
|
||||
m.FileMeta.Focus.X = 3.6
|
||||
err = validate.Struct(m)
|
||||
suite.EqualError(err, "Key: 'MediaAttachment.FileMeta.Focus.X' Error:Field validation for 'X' failed on the 'max' tag")
|
||||
|
||||
m.FileMeta.Focus.Y = -50
|
||||
err = validate.Struct(m)
|
||||
suite.EqualError(err, "Key: 'MediaAttachment.FileMeta.Focus.X' Error:Field validation for 'X' failed on the 'max' tag\nKey: 'MediaAttachment.FileMeta.Focus.Y' Error:Field validation for 'Y' failed on the 'min' tag")
|
||||
}
|
||||
|
||||
func (suite *MediaAttachmentValidateTestSuite) TestValidateMediaAttachmentBadURLCombos() {
|
||||
m := happyMediaAttachment()
|
||||
|
||||
m.URL = "aaaaaaaaaa"
|
||||
err := validate.Struct(m)
|
||||
suite.EqualError(err, "Key: 'MediaAttachment.URL' Error:Field validation for 'URL' failed on the 'url' tag")
|
||||
|
||||
m.URL = ""
|
||||
err = validate.Struct(m)
|
||||
suite.EqualError(err, "Key: 'MediaAttachment.URL' Error:Field validation for 'URL' failed on the 'required_without' tag\nKey: 'MediaAttachment.RemoteURL' Error:Field validation for 'RemoteURL' failed on the 'required_without' tag")
|
||||
|
||||
m.RemoteURL = "oooooooooo"
|
||||
err = validate.Struct(m)
|
||||
suite.EqualError(err, "Key: 'MediaAttachment.RemoteURL' Error:Field validation for 'RemoteURL' failed on the 'url' tag")
|
||||
|
||||
m.RemoteURL = "https://a-valid-url.gay"
|
||||
err = validate.Struct(m)
|
||||
suite.NoError(err)
|
||||
}
|
||||
|
||||
func (suite *MediaAttachmentValidateTestSuite) TestValidateMediaAttachmentBlurhash() {
|
||||
m := happyMediaAttachment()
|
||||
|
||||
m.Blurhash = ""
|
||||
err := validate.Struct(m)
|
||||
suite.EqualError(err, "Key: 'MediaAttachment.Blurhash' Error:Field validation for 'Blurhash' failed on the 'required_if' tag")
|
||||
|
||||
m.Type = gtsmodel.FileTypeAudio
|
||||
err = validate.Struct(m)
|
||||
suite.NoError(err)
|
||||
|
||||
m.Blurhash = "some_blurhash"
|
||||
err = validate.Struct(m)
|
||||
suite.NoError(err)
|
||||
}
|
||||
|
||||
func (suite *MediaAttachmentValidateTestSuite) TestValidateMediaAttachmentProcessing() {
|
||||
m := happyMediaAttachment()
|
||||
|
||||
m.Processing = 420
|
||||
err := validate.Struct(m)
|
||||
suite.EqualError(err, "Key: 'MediaAttachment.Processing' Error:Field validation for 'Processing' failed on the 'oneof' tag")
|
||||
|
||||
m.Processing = -5
|
||||
err = validate.Struct(m)
|
||||
suite.EqualError(err, "Key: 'MediaAttachment.Processing' Error:Field validation for 'Processing' failed on the 'oneof' tag")
|
||||
}
|
||||
|
||||
func TestMediaAttachmentValidateTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(MediaAttachmentValidateTestSuite))
|
||||
}
|
|
@ -1,101 +0,0 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// 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 validate_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/validate"
|
||||
)
|
||||
|
||||
func happyMention() *gtsmodel.Mention {
|
||||
return >smodel.Mention{
|
||||
ID: "01FE91RJR88PSEEE30EV35QR8N",
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
OriginAccountID: "01FE96MAE58MXCE5C4SSMEMCEK",
|
||||
OriginAccountURI: "https://some-instance/accounts/bleepbloop",
|
||||
OriginAccount: nil,
|
||||
TargetAccountID: "01FE96MXRHWZHKC0WH5FT82H1A",
|
||||
TargetAccount: nil,
|
||||
StatusID: "01FE96NBPNJNY26730FT6GZTFE",
|
||||
Status: nil,
|
||||
}
|
||||
}
|
||||
|
||||
type MentionValidateTestSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func (suite *MentionValidateTestSuite) TestValidateMentionHappyPath() {
|
||||
// no problem here
|
||||
m := happyMention()
|
||||
err := validate.Struct(m)
|
||||
suite.NoError(err)
|
||||
}
|
||||
|
||||
func (suite *MentionValidateTestSuite) TestValidateMentionBadID() {
|
||||
m := happyMention()
|
||||
|
||||
m.ID = ""
|
||||
err := validate.Struct(m)
|
||||
suite.EqualError(err, "Key: 'Mention.ID' Error:Field validation for 'ID' failed on the 'required' tag")
|
||||
|
||||
m.ID = "01FE96W293ZPRG9FQQP48HK8N001FE96W32AT24VYBGM12WN3GKB"
|
||||
err = validate.Struct(m)
|
||||
suite.EqualError(err, "Key: 'Mention.ID' Error:Field validation for 'ID' failed on the 'ulid' tag")
|
||||
}
|
||||
|
||||
func (suite *MentionValidateTestSuite) TestValidateMentionAccountURI() {
|
||||
m := happyMention()
|
||||
|
||||
m.OriginAccountURI = ""
|
||||
err := validate.Struct(m)
|
||||
suite.EqualError(err, "Key: 'Mention.OriginAccountURI' Error:Field validation for 'OriginAccountURI' failed on the 'url' tag")
|
||||
|
||||
m.OriginAccountURI = "---------------------------"
|
||||
err = validate.Struct(m)
|
||||
suite.EqualError(err, "Key: 'Mention.OriginAccountURI' Error:Field validation for 'OriginAccountURI' failed on the 'url' tag")
|
||||
}
|
||||
|
||||
func (suite *MentionValidateTestSuite) TestValidateMentionDodgyStatusID() {
|
||||
m := happyMention()
|
||||
|
||||
m.StatusID = "9HZJ76B6VXSKF"
|
||||
err := validate.Struct(m)
|
||||
suite.EqualError(err, "Key: 'Mention.StatusID' Error:Field validation for 'StatusID' failed on the 'ulid' tag")
|
||||
|
||||
m.StatusID = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!!!!!!!!!!!!"
|
||||
err = validate.Struct(m)
|
||||
suite.EqualError(err, "Key: 'Mention.StatusID' Error:Field validation for 'StatusID' failed on the 'ulid' tag")
|
||||
}
|
||||
|
||||
func (suite *MentionValidateTestSuite) TestValidateMentionNoCreatedAt() {
|
||||
m := happyMention()
|
||||
|
||||
m.CreatedAt = time.Time{}
|
||||
err := validate.Struct(m)
|
||||
suite.NoError(err)
|
||||
}
|
||||
|
||||
func TestMentionValidateTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(MentionValidateTestSuite))
|
||||
}
|
|
@ -1,97 +0,0 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// 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 validate_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/validate"
|
||||
)
|
||||
|
||||
func happyNotification() *gtsmodel.Notification {
|
||||
return >smodel.Notification{
|
||||
ID: "01FE91RJR88PSEEE30EV35QR8N",
|
||||
CreatedAt: time.Now(),
|
||||
NotificationType: gtsmodel.NotificationFave,
|
||||
OriginAccountID: "01FE96MAE58MXCE5C4SSMEMCEK",
|
||||
OriginAccount: nil,
|
||||
TargetAccountID: "01FE96MXRHWZHKC0WH5FT82H1A",
|
||||
TargetAccount: nil,
|
||||
StatusID: "01FE96NBPNJNY26730FT6GZTFE",
|
||||
Status: nil,
|
||||
}
|
||||
}
|
||||
|
||||
type NotificationValidateTestSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func (suite *NotificationValidateTestSuite) TestValidateNotificationHappyPath() {
|
||||
// no problem here
|
||||
n := happyNotification()
|
||||
err := validate.Struct(n)
|
||||
suite.NoError(err)
|
||||
}
|
||||
|
||||
func (suite *NotificationValidateTestSuite) TestValidateNotificationBadID() {
|
||||
n := happyNotification()
|
||||
|
||||
n.ID = ""
|
||||
err := validate.Struct(n)
|
||||
suite.EqualError(err, "Key: 'Notification.ID' Error:Field validation for 'ID' failed on the 'required' tag")
|
||||
|
||||
n.ID = "01FE96W293ZPRG9FQQP48HK8N001FE96W32AT24VYBGM12WN3GKB"
|
||||
err = validate.Struct(n)
|
||||
suite.EqualError(err, "Key: 'Notification.ID' Error:Field validation for 'ID' failed on the 'ulid' tag")
|
||||
}
|
||||
|
||||
func (suite *NotificationValidateTestSuite) TestValidateNotificationStatusID() {
|
||||
n := happyNotification()
|
||||
|
||||
n.StatusID = ""
|
||||
err := validate.Struct(n)
|
||||
suite.EqualError(err, "Key: 'Notification.StatusID' Error:Field validation for 'StatusID' failed on the 'required_if' tag")
|
||||
|
||||
n.StatusID = "9HZJ76B6VXSKF"
|
||||
err = validate.Struct(n)
|
||||
suite.EqualError(err, "Key: 'Notification.StatusID' Error:Field validation for 'StatusID' failed on the 'ulid' tag")
|
||||
|
||||
n.StatusID = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!!!!!!!!!!!!"
|
||||
err = validate.Struct(n)
|
||||
suite.EqualError(err, "Key: 'Notification.StatusID' Error:Field validation for 'StatusID' failed on the 'ulid' tag")
|
||||
|
||||
n.StatusID = ""
|
||||
n.NotificationType = gtsmodel.NotificationFollowRequest
|
||||
err = validate.Struct(n)
|
||||
suite.NoError(err)
|
||||
}
|
||||
|
||||
func (suite *NotificationValidateTestSuite) TestValidateNotificationNoCreatedAt() {
|
||||
n := happyNotification()
|
||||
|
||||
n.CreatedAt = time.Time{}
|
||||
err := validate.Struct(n)
|
||||
suite.NoError(err)
|
||||
}
|
||||
|
||||
func TestNotificationValidateTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(NotificationValidateTestSuite))
|
||||
}
|
|
@ -1,87 +0,0 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// 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 validate_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/validate"
|
||||
)
|
||||
|
||||
func happyRouterSession() *gtsmodel.RouterSession {
|
||||
return >smodel.RouterSession{
|
||||
ID: "01FE91RJR88PSEEE30EV35QR8N",
|
||||
Auth: []byte("12345678901234567890123456789012"),
|
||||
Crypt: []byte("12345678901234567890123456789012"),
|
||||
}
|
||||
}
|
||||
|
||||
type RouterSessionValidateTestSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func (suite *RouterSessionValidateTestSuite) TestValidateRouterSessionHappyPath() {
|
||||
// no problem here
|
||||
r := happyRouterSession()
|
||||
err := validate.Struct(r)
|
||||
suite.NoError(err)
|
||||
}
|
||||
|
||||
func (suite *RouterSessionValidateTestSuite) TestValidateRouterSessionAuth() {
|
||||
r := happyRouterSession()
|
||||
|
||||
// remove auth struct
|
||||
r.Auth = nil
|
||||
err := validate.Struct(r)
|
||||
suite.EqualError(err, "Key: 'RouterSession.Auth' Error:Field validation for 'Auth' failed on the 'required' tag")
|
||||
|
||||
// auth bytes too long
|
||||
r.Auth = []byte("1234567890123456789012345678901234567890")
|
||||
err = validate.Struct(r)
|
||||
suite.EqualError(err, "Key: 'RouterSession.Auth' Error:Field validation for 'Auth' failed on the 'len' tag")
|
||||
|
||||
// auth bytes too short
|
||||
r.Auth = []byte("12345678901")
|
||||
err = validate.Struct(r)
|
||||
suite.EqualError(err, "Key: 'RouterSession.Auth' Error:Field validation for 'Auth' failed on the 'len' tag")
|
||||
}
|
||||
|
||||
func (suite *RouterSessionValidateTestSuite) TestValidateRouterSessionCrypt() {
|
||||
r := happyRouterSession()
|
||||
|
||||
// remove crypt struct
|
||||
r.Crypt = nil
|
||||
err := validate.Struct(r)
|
||||
suite.EqualError(err, "Key: 'RouterSession.Crypt' Error:Field validation for 'Crypt' failed on the 'required' tag")
|
||||
|
||||
// crypt bytes too long
|
||||
r.Crypt = []byte("1234567890123456789012345678901234567890")
|
||||
err = validate.Struct(r)
|
||||
suite.EqualError(err, "Key: 'RouterSession.Crypt' Error:Field validation for 'Crypt' failed on the 'len' tag")
|
||||
|
||||
// crypt bytes too short
|
||||
r.Crypt = []byte("12345678901")
|
||||
err = validate.Struct(r)
|
||||
suite.EqualError(err, "Key: 'RouterSession.Crypt' Error:Field validation for 'Crypt' failed on the 'len' tag")
|
||||
}
|
||||
|
||||
func TestRouterSessionValidateTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(RouterSessionValidateTestSuite))
|
||||
}
|
|
@ -1,160 +0,0 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// 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 validate_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/ap"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/validate"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
)
|
||||
|
||||
func happyStatus() *gtsmodel.Status {
|
||||
return >smodel.Status{
|
||||
ID: "01FEBBH6NYDG87NK6A6EC543ED",
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
URI: "https://example.org/users/test_user/statuses/01FEBBH6NYDG87NK6A6EC543ED",
|
||||
URL: "https://example.org/@test_user/01FEBBH6NYDG87NK6A6EC543ED",
|
||||
Content: "<p>Test status! #hello</p>",
|
||||
AttachmentIDs: []string{"01FEBBKZBY9H5FEP3PHVVAAGN1", "01FEBBM7S2R4WT6WWW22KN1PWE"},
|
||||
Attachments: nil,
|
||||
TagIDs: []string{"01FEBBNBMBSN1FESMZ1TCXNWYP"},
|
||||
Tags: nil,
|
||||
MentionIDs: nil,
|
||||
Mentions: nil,
|
||||
EmojiIDs: nil,
|
||||
Emojis: nil,
|
||||
Local: testrig.TrueBool(),
|
||||
AccountID: "01FEBBQ4KEP3824WW61MF52638",
|
||||
Account: nil,
|
||||
AccountURI: "https://example.org/users/test_user",
|
||||
InReplyToID: "",
|
||||
InReplyToURI: "",
|
||||
InReplyToAccountID: "",
|
||||
InReplyTo: nil,
|
||||
InReplyToAccount: nil,
|
||||
BoostOfID: "",
|
||||
BoostOfAccountID: "",
|
||||
BoostOf: nil,
|
||||
BoostOfAccount: nil,
|
||||
ContentWarning: "hello world test post",
|
||||
Visibility: gtsmodel.VisibilityPublic,
|
||||
Sensitive: testrig.FalseBool(),
|
||||
Language: "en",
|
||||
CreatedWithApplicationID: "01FEBBZHF4GFVRXSJVXD0JTZZ2",
|
||||
CreatedWithApplication: nil,
|
||||
Federated: testrig.TrueBool(),
|
||||
Boostable: testrig.TrueBool(),
|
||||
Replyable: testrig.TrueBool(),
|
||||
Likeable: testrig.TrueBool(),
|
||||
ActivityStreamsType: ap.ObjectNote,
|
||||
Text: "Test status! #hello",
|
||||
}
|
||||
}
|
||||
|
||||
type StatusValidateTestSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func (suite *StatusValidateTestSuite) TestValidateStatusHappyPath() {
|
||||
// no problem here
|
||||
s := happyStatus()
|
||||
err := validate.Struct(s)
|
||||
suite.NoError(err)
|
||||
}
|
||||
|
||||
func (suite *StatusValidateTestSuite) TestValidateStatusBadID() {
|
||||
s := happyStatus()
|
||||
|
||||
s.ID = ""
|
||||
err := validate.Struct(s)
|
||||
suite.EqualError(err, "Key: 'Status.ID' Error:Field validation for 'ID' failed on the 'required' tag")
|
||||
|
||||
s.ID = "01FE96W293ZPRG9FQQP48HK8N001FE96W32AT24VYBGM12WN3GKB"
|
||||
err = validate.Struct(s)
|
||||
suite.EqualError(err, "Key: 'Status.ID' Error:Field validation for 'ID' failed on the 'ulid' tag")
|
||||
}
|
||||
|
||||
func (suite *StatusValidateTestSuite) TestValidateStatusAttachmentIDs() {
|
||||
s := happyStatus()
|
||||
|
||||
s.AttachmentIDs[0] = ""
|
||||
err := validate.Struct(s)
|
||||
suite.EqualError(err, "Key: 'Status.AttachmentIDs[0]' Error:Field validation for 'AttachmentIDs[0]' failed on the 'ulid' tag")
|
||||
|
||||
s.AttachmentIDs[0] = "01FE96W293ZPRG9FQQP48HK8N001FE96W32AT24VYBGM12WN3GKB"
|
||||
err = validate.Struct(s)
|
||||
suite.EqualError(err, "Key: 'Status.AttachmentIDs[0]' Error:Field validation for 'AttachmentIDs[0]' failed on the 'ulid' tag")
|
||||
|
||||
s.AttachmentIDs[1] = ""
|
||||
err = validate.Struct(s)
|
||||
suite.EqualError(err, "Key: 'Status.AttachmentIDs[0]' Error:Field validation for 'AttachmentIDs[0]' failed on the 'ulid' tag\nKey: 'Status.AttachmentIDs[1]' Error:Field validation for 'AttachmentIDs[1]' failed on the 'ulid' tag")
|
||||
|
||||
s.AttachmentIDs = []string{}
|
||||
err = validate.Struct(s)
|
||||
suite.NoError(err)
|
||||
|
||||
s.AttachmentIDs = nil
|
||||
err = validate.Struct(s)
|
||||
suite.NoError(err)
|
||||
}
|
||||
|
||||
func (suite *StatusValidateTestSuite) TestStatusApplicationID() {
|
||||
s := happyStatus()
|
||||
|
||||
s.CreatedWithApplicationID = ""
|
||||
err := validate.Struct(s)
|
||||
suite.EqualError(err, "Key: 'Status.CreatedWithApplicationID' Error:Field validation for 'CreatedWithApplicationID' failed on the 'required_if' tag")
|
||||
|
||||
s.Local = testrig.FalseBool()
|
||||
err = validate.Struct(s)
|
||||
suite.NoError(err)
|
||||
}
|
||||
|
||||
func (suite *StatusValidateTestSuite) TestValidateStatusReplyFields() {
|
||||
s := happyStatus()
|
||||
|
||||
s.InReplyToAccountID = "01FEBCTP6DN7961PN81C3DVM4N "
|
||||
err := validate.Struct(s)
|
||||
suite.EqualError(err, "Key: 'Status.InReplyToID' Error:Field validation for 'InReplyToID' failed on the 'required_with' tag\nKey: 'Status.InReplyToURI' Error:Field validation for 'InReplyToURI' failed on the 'required_with' tag\nKey: 'Status.InReplyToAccountID' Error:Field validation for 'InReplyToAccountID' failed on the 'ulid' tag")
|
||||
|
||||
s.InReplyToAccountID = "01FEBCTP6DN7961PN81C3DVM4N"
|
||||
err = validate.Struct(s)
|
||||
suite.EqualError(err, "Key: 'Status.InReplyToID' Error:Field validation for 'InReplyToID' failed on the 'required_with' tag\nKey: 'Status.InReplyToURI' Error:Field validation for 'InReplyToURI' failed on the 'required_with' tag")
|
||||
|
||||
s.InReplyToURI = "https://example.org/users/mmbop/statuses/aaaaaaaa"
|
||||
err = validate.Struct(s)
|
||||
suite.EqualError(err, "Key: 'Status.InReplyToID' Error:Field validation for 'InReplyToID' failed on the 'required_with' tag")
|
||||
|
||||
s.InReplyToID = "not a valid ulid"
|
||||
err = validate.Struct(s)
|
||||
suite.EqualError(err, "Key: 'Status.InReplyToID' Error:Field validation for 'InReplyToID' failed on the 'ulid' tag")
|
||||
|
||||
s.InReplyToID = "01FEBD07E72DEY6YB9K10ZA6ST"
|
||||
err = validate.Struct(s)
|
||||
suite.NoError(err)
|
||||
}
|
||||
|
||||
func TestStatusValidateTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(StatusValidateTestSuite))
|
||||
}
|
|
@ -1,87 +0,0 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// 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 validate_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/validate"
|
||||
)
|
||||
|
||||
func happyStatusBookmark() *gtsmodel.StatusBookmark {
|
||||
return >smodel.StatusBookmark{
|
||||
ID: "01FE91RJR88PSEEE30EV35QR8N",
|
||||
CreatedAt: time.Now(),
|
||||
AccountID: "01FE96MAE58MXCE5C4SSMEMCEK",
|
||||
Account: nil,
|
||||
TargetAccountID: "01FE96MXRHWZHKC0WH5FT82H1A",
|
||||
TargetAccount: nil,
|
||||
StatusID: "01FE96NBPNJNY26730FT6GZTFE",
|
||||
Status: nil,
|
||||
}
|
||||
}
|
||||
|
||||
type StatusBookmarkValidateTestSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func (suite *StatusBookmarkValidateTestSuite) TestValidateStatusBookmarkHappyPath() {
|
||||
// no problem here
|
||||
s := happyStatusBookmark()
|
||||
err := validate.Struct(s)
|
||||
suite.NoError(err)
|
||||
}
|
||||
|
||||
func (suite *StatusBookmarkValidateTestSuite) TestValidateStatusBookmarkBadID() {
|
||||
s := happyStatusBookmark()
|
||||
|
||||
s.ID = ""
|
||||
err := validate.Struct(s)
|
||||
suite.EqualError(err, "Key: 'StatusBookmark.ID' Error:Field validation for 'ID' failed on the 'required' tag")
|
||||
|
||||
s.ID = "01FE96W293ZPRG9FQQP48HK8N001FE96W32AT24VYBGM12WN3GKB"
|
||||
err = validate.Struct(s)
|
||||
suite.EqualError(err, "Key: 'StatusBookmark.ID' Error:Field validation for 'ID' failed on the 'ulid' tag")
|
||||
}
|
||||
|
||||
func (suite *StatusBookmarkValidateTestSuite) TestValidateStatusBookmarkDodgyStatusID() {
|
||||
s := happyStatusBookmark()
|
||||
|
||||
s.StatusID = "9HZJ76B6VXSKF"
|
||||
err := validate.Struct(s)
|
||||
suite.EqualError(err, "Key: 'StatusBookmark.StatusID' Error:Field validation for 'StatusID' failed on the 'ulid' tag")
|
||||
|
||||
s.StatusID = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!!!!!!!!!!!!"
|
||||
err = validate.Struct(s)
|
||||
suite.EqualError(err, "Key: 'StatusBookmark.StatusID' Error:Field validation for 'StatusID' failed on the 'ulid' tag")
|
||||
}
|
||||
|
||||
func (suite *StatusBookmarkValidateTestSuite) TestValidateStatusBookmarkNoCreatedAt() {
|
||||
s := happyStatusBookmark()
|
||||
|
||||
s.CreatedAt = time.Time{}
|
||||
err := validate.Struct(s)
|
||||
suite.NoError(err)
|
||||
}
|
||||
|
||||
func TestStatusBookmarkValidateTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(StatusBookmarkValidateTestSuite))
|
||||
}
|
|
@ -1,100 +0,0 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// 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 validate_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/validate"
|
||||
)
|
||||
|
||||
func happyStatusFave() *gtsmodel.StatusFave {
|
||||
return >smodel.StatusFave{
|
||||
ID: "01FE91RJR88PSEEE30EV35QR8N",
|
||||
CreatedAt: time.Now(),
|
||||
AccountID: "01FE96MAE58MXCE5C4SSMEMCEK",
|
||||
Account: nil,
|
||||
TargetAccountID: "01FE96MXRHWZHKC0WH5FT82H1A",
|
||||
TargetAccount: nil,
|
||||
StatusID: "01FE96NBPNJNY26730FT6GZTFE",
|
||||
Status: nil,
|
||||
URI: "https://example.org/users/user1/activity/faves/01FE91RJR88PSEEE30EV35QR8N",
|
||||
}
|
||||
}
|
||||
|
||||
type StatusFaveValidateTestSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func (suite *StatusFaveValidateTestSuite) TestValidateStatusFaveHappyPath() {
|
||||
// no problem here
|
||||
f := happyStatusFave()
|
||||
err := validate.Struct(f)
|
||||
suite.NoError(err)
|
||||
}
|
||||
|
||||
func (suite *StatusFaveValidateTestSuite) TestValidateStatusFaveBadID() {
|
||||
f := happyStatusFave()
|
||||
|
||||
f.ID = ""
|
||||
err := validate.Struct(f)
|
||||
suite.EqualError(err, "Key: 'StatusFave.ID' Error:Field validation for 'ID' failed on the 'required' tag")
|
||||
|
||||
f.ID = "01FE96W293ZPRG9FQQP48HK8N001FE96W32AT24VYBGM12WN3GKB"
|
||||
err = validate.Struct(f)
|
||||
suite.EqualError(err, "Key: 'StatusFave.ID' Error:Field validation for 'ID' failed on the 'ulid' tag")
|
||||
}
|
||||
|
||||
func (suite *StatusFaveValidateTestSuite) TestValidateStatusFaveDodgyStatusID() {
|
||||
f := happyStatusFave()
|
||||
|
||||
f.StatusID = "9HZJ76B6VXSKF"
|
||||
err := validate.Struct(f)
|
||||
suite.EqualError(err, "Key: 'StatusFave.StatusID' Error:Field validation for 'StatusID' failed on the 'ulid' tag")
|
||||
|
||||
f.StatusID = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!!!!!!!!!!!!"
|
||||
err = validate.Struct(f)
|
||||
suite.EqualError(err, "Key: 'StatusFave.StatusID' Error:Field validation for 'StatusID' failed on the 'ulid' tag")
|
||||
}
|
||||
|
||||
func (suite *StatusFaveValidateTestSuite) TestValidateStatusFaveNoCreatedAt() {
|
||||
f := happyStatusFave()
|
||||
|
||||
f.CreatedAt = time.Time{}
|
||||
err := validate.Struct(f)
|
||||
suite.NoError(err)
|
||||
}
|
||||
|
||||
func (suite *StatusFaveValidateTestSuite) TestValidateStatusFaveNoURI() {
|
||||
f := happyStatusFave()
|
||||
|
||||
f.URI = ""
|
||||
err := validate.Struct(f)
|
||||
suite.EqualError(err, "Key: 'StatusFave.URI' Error:Field validation for 'URI' failed on the 'required' tag")
|
||||
|
||||
f.URI = "this-is-not-a-valid-url"
|
||||
err = validate.Struct(f)
|
||||
suite.EqualError(err, "Key: 'StatusFave.URI' Error:Field validation for 'URI' failed on the 'url' tag")
|
||||
}
|
||||
|
||||
func TestStatusFaveValidateTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(StatusFaveValidateTestSuite))
|
||||
}
|
|
@ -1,87 +0,0 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// 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 validate_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/validate"
|
||||
)
|
||||
|
||||
func happyStatusMute() *gtsmodel.StatusMute {
|
||||
return >smodel.StatusMute{
|
||||
ID: "01FE91RJR88PSEEE30EV35QR8N",
|
||||
CreatedAt: time.Now(),
|
||||
AccountID: "01FE96MAE58MXCE5C4SSMEMCEK",
|
||||
Account: nil,
|
||||
TargetAccountID: "01FE96MXRHWZHKC0WH5FT82H1A",
|
||||
TargetAccount: nil,
|
||||
StatusID: "01FE96NBPNJNY26730FT6GZTFE",
|
||||
Status: nil,
|
||||
}
|
||||
}
|
||||
|
||||
type StatusMuteValidateTestSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func (suite *StatusMuteValidateTestSuite) TestValidateStatusMuteHappyPath() {
|
||||
// no problem here
|
||||
m := happyStatusMute()
|
||||
err := validate.Struct(m)
|
||||
suite.NoError(err)
|
||||
}
|
||||
|
||||
func (suite *StatusMuteValidateTestSuite) TestValidateStatusMuteBadID() {
|
||||
m := happyStatusMute()
|
||||
|
||||
m.ID = ""
|
||||
err := validate.Struct(m)
|
||||
suite.EqualError(err, "Key: 'StatusMute.ID' Error:Field validation for 'ID' failed on the 'required' tag")
|
||||
|
||||
m.ID = "01FE96W293ZPRG9FQQP48HK8N001FE96W32AT24VYBGM12WN3GKB"
|
||||
err = validate.Struct(m)
|
||||
suite.EqualError(err, "Key: 'StatusMute.ID' Error:Field validation for 'ID' failed on the 'ulid' tag")
|
||||
}
|
||||
|
||||
func (suite *StatusMuteValidateTestSuite) TestValidateStatusMuteDodgyStatusID() {
|
||||
m := happyStatusMute()
|
||||
|
||||
m.StatusID = "9HZJ76B6VXSKF"
|
||||
err := validate.Struct(m)
|
||||
suite.EqualError(err, "Key: 'StatusMute.StatusID' Error:Field validation for 'StatusID' failed on the 'ulid' tag")
|
||||
|
||||
m.StatusID = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!!!!!!!!!!!!"
|
||||
err = validate.Struct(m)
|
||||
suite.EqualError(err, "Key: 'StatusMute.StatusID' Error:Field validation for 'StatusID' failed on the 'ulid' tag")
|
||||
}
|
||||
|
||||
func (suite *StatusMuteValidateTestSuite) TestValidateStatusMuteNoCreatedAt() {
|
||||
m := happyStatusMute()
|
||||
|
||||
m.CreatedAt = time.Time{}
|
||||
err := validate.Struct(m)
|
||||
suite.NoError(err)
|
||||
}
|
||||
|
||||
func TestStatusMuteValidateTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(StatusMuteValidateTestSuite))
|
||||
}
|
|
@ -1,67 +0,0 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// 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 validate
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/regexes"
|
||||
)
|
||||
|
||||
var v *validator.Validate
|
||||
|
||||
func ulidValidator(fl validator.FieldLevel) bool {
|
||||
field := fl.Field()
|
||||
|
||||
switch field.Kind() {
|
||||
case reflect.String:
|
||||
return regexes.ULID.MatchString(field.String())
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
v = validator.New()
|
||||
if err := v.RegisterValidation("ulid", ulidValidator); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Struct validates the passed struct, returning validator.ValidationErrors if invalid, or nil if OK.
|
||||
func Struct(s interface{}) error {
|
||||
return processValidationError(v.Struct(s))
|
||||
}
|
||||
|
||||
func processValidationError(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if ive, ok := err.(*validator.InvalidValidationError); ok {
|
||||
panic(ive)
|
||||
}
|
||||
|
||||
valErr, ok := err.(validator.ValidationErrors)
|
||||
if !ok {
|
||||
panic("*validator.InvalidValidationError could not be coerced to validator.ValidationErrors")
|
||||
}
|
||||
|
||||
return valErr
|
||||
}
|
|
@ -1,70 +0,0 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// 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 validate_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/validate"
|
||||
)
|
||||
|
||||
type ValidateTestSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func (suite *ValidateTestSuite) TestValidateNilPointer() {
|
||||
var nilUser *gtsmodel.User
|
||||
suite.Panics(func() {
|
||||
validate.Struct(nilUser)
|
||||
})
|
||||
}
|
||||
|
||||
func (suite *ValidateTestSuite) TestValidatePointer() {
|
||||
user := >smodel.User{}
|
||||
err := validate.Struct(user)
|
||||
suite.EqualError(err, "Key: 'User.ID' Error:Field validation for 'ID' failed on the 'required' tag\nKey: 'User.AccountID' Error:Field validation for 'AccountID' failed on the 'required' tag\nKey: 'User.EncryptedPassword' Error:Field validation for 'EncryptedPassword' failed on the 'required' tag\nKey: 'User.UnconfirmedEmail' Error:Field validation for 'UnconfirmedEmail' failed on the 'required_without' tag")
|
||||
}
|
||||
|
||||
func (suite *ValidateTestSuite) TestValidateNil() {
|
||||
suite.Panics(func() {
|
||||
validate.Struct(nil)
|
||||
})
|
||||
}
|
||||
|
||||
func (suite *ValidateTestSuite) TestValidateWeirdULID() {
|
||||
type a struct {
|
||||
ID bool `validate:"required,ulid"`
|
||||
}
|
||||
|
||||
err := validate.Struct(a{ID: true})
|
||||
suite.Error(err)
|
||||
}
|
||||
|
||||
func (suite *ValidateTestSuite) TestValidateNotStruct() {
|
||||
type aaaaaaa string
|
||||
aaaaaa := aaaaaaa("aaaa")
|
||||
suite.Panics(func() {
|
||||
validate.Struct(aaaaaa)
|
||||
})
|
||||
}
|
||||
|
||||
func TestValidateTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(ValidateTestSuite))
|
||||
}
|
|
@ -1,98 +0,0 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// 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 validate_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/validate"
|
||||
)
|
||||
|
||||
func happyToken() *gtsmodel.Token {
|
||||
return >smodel.Token{
|
||||
ID: "01FE91RJR88PSEEE30EV35QR8N",
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
ClientID: "01FEEDMF6C0QD589MRK7919Z0R",
|
||||
UserID: "01FEK0BFJKYXB4Y51RBQ7P5P79",
|
||||
RedirectURI: "oauth2redirect://com.keylesspalace.tusky/",
|
||||
Scope: "read write follow",
|
||||
}
|
||||
}
|
||||
|
||||
type TokenValidateTestSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func (suite *TokenValidateTestSuite) TestValidateTokenHappyPath() {
|
||||
// no problem here
|
||||
t := happyToken()
|
||||
err := validate.Struct(t)
|
||||
suite.NoError(err)
|
||||
}
|
||||
|
||||
func (suite *TokenValidateTestSuite) TestValidateTokenBadID() {
|
||||
t := happyToken()
|
||||
|
||||
t.ID = ""
|
||||
err := validate.Struct(t)
|
||||
suite.EqualError(err, "Key: 'Token.ID' Error:Field validation for 'ID' failed on the 'required' tag")
|
||||
|
||||
t.ID = "01FE96W293ZPRG9FQQP48HK8N001FE96W32AT24VYBGM12WN3GKB"
|
||||
err = validate.Struct(t)
|
||||
suite.EqualError(err, "Key: 'Token.ID' Error:Field validation for 'ID' failed on the 'ulid' tag")
|
||||
}
|
||||
|
||||
func (suite *TokenValidateTestSuite) TestValidateTokenNoCreatedAt() {
|
||||
t := happyToken()
|
||||
|
||||
t.CreatedAt = time.Time{}
|
||||
err := validate.Struct(t)
|
||||
suite.NoError(err)
|
||||
}
|
||||
|
||||
func (suite *TokenValidateTestSuite) TestValidateTokenRedirectURI() {
|
||||
t := happyToken()
|
||||
|
||||
t.RedirectURI = "invalid-uri"
|
||||
err := validate.Struct(t)
|
||||
suite.EqualError(err, "Key: 'Token.RedirectURI' Error:Field validation for 'RedirectURI' failed on the 'uri' tag")
|
||||
|
||||
t.RedirectURI = ""
|
||||
err = validate.Struct(t)
|
||||
suite.EqualError(err, "Key: 'Token.RedirectURI' Error:Field validation for 'RedirectURI' failed on the 'required' tag")
|
||||
|
||||
t.RedirectURI = "urn:ietf:wg:oauth:2.0:oob"
|
||||
err = validate.Struct(t)
|
||||
suite.NoError(err)
|
||||
}
|
||||
|
||||
func (suite *TokenValidateTestSuite) TestValidateTokenScope() {
|
||||
t := happyToken()
|
||||
|
||||
t.Scope = ""
|
||||
err := validate.Struct(t)
|
||||
suite.EqualError(err, "Key: 'Token.Scope' Error:Field validation for 'Scope' failed on the 'required' tag")
|
||||
}
|
||||
|
||||
func TestTokenValidateTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(TokenValidateTestSuite))
|
||||
}
|
|
@ -1,134 +0,0 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// 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 validate_test
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/validate"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
)
|
||||
|
||||
func happyUser() *gtsmodel.User {
|
||||
return >smodel.User{
|
||||
ID: "01FE8TTK9F34BR0KG7639AJQTX",
|
||||
Email: "whatever@example.org",
|
||||
AccountID: "01FE8TWA7CN8J7237K5DFS1RY5",
|
||||
Account: nil,
|
||||
EncryptedPassword: "$2y$10$tkRapNGW.RWkEuCMWdgArunABFvsPGRvFQY3OibfSJo0RDL3z8WfC",
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
SignUpIP: net.ParseIP("128.64.32.16"),
|
||||
CurrentSignInAt: time.Now(),
|
||||
CurrentSignInIP: net.ParseIP("128.64.32.16"),
|
||||
LastSignInAt: time.Now(),
|
||||
LastSignInIP: net.ParseIP("128.64.32.16"),
|
||||
SignInCount: 0,
|
||||
InviteID: "",
|
||||
ChosenLanguages: []string{},
|
||||
FilteredLanguages: []string{},
|
||||
Locale: "en",
|
||||
CreatedByApplicationID: "01FE8Y5EHMWCA1MHMTNHRVZ1X4",
|
||||
CreatedByApplication: nil,
|
||||
LastEmailedAt: time.Now(),
|
||||
ConfirmationToken: "",
|
||||
ConfirmedAt: time.Now(),
|
||||
ConfirmationSentAt: time.Time{},
|
||||
UnconfirmedEmail: "",
|
||||
Moderator: testrig.FalseBool(),
|
||||
Admin: testrig.FalseBool(),
|
||||
Disabled: testrig.FalseBool(),
|
||||
Approved: testrig.TrueBool(),
|
||||
}
|
||||
}
|
||||
|
||||
type UserValidateTestSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
func (suite *UserValidateTestSuite) TestValidateUserHappyPath() {
|
||||
// no problem here
|
||||
u := happyUser()
|
||||
err := validate.Struct(u)
|
||||
suite.NoError(err)
|
||||
}
|
||||
|
||||
func (suite *UserValidateTestSuite) TestValidateUserNoID() {
|
||||
// user has no id set
|
||||
u := happyUser()
|
||||
u.ID = ""
|
||||
|
||||
err := validate.Struct(u)
|
||||
suite.EqualError(err, "Key: 'User.ID' Error:Field validation for 'ID' failed on the 'required' tag")
|
||||
}
|
||||
|
||||
func (suite *UserValidateTestSuite) TestValidateUserNoEmail() {
|
||||
// user has no email or unconfirmed email set
|
||||
u := happyUser()
|
||||
u.Email = ""
|
||||
|
||||
err := validate.Struct(u)
|
||||
suite.EqualError(err, "Key: 'User.Email' Error:Field validation for 'Email' failed on the 'required_with' tag\nKey: 'User.UnconfirmedEmail' Error:Field validation for 'UnconfirmedEmail' failed on the 'required_without' tag")
|
||||
}
|
||||
|
||||
func (suite *UserValidateTestSuite) TestValidateUserOnlyUnconfirmedEmail() {
|
||||
// user has only UnconfirmedEmail but ConfirmedAt is set
|
||||
u := happyUser()
|
||||
u.Email = ""
|
||||
u.UnconfirmedEmail = "whatever@example.org"
|
||||
|
||||
err := validate.Struct(u)
|
||||
suite.EqualError(err, "Key: 'User.Email' Error:Field validation for 'Email' failed on the 'required_with' tag")
|
||||
}
|
||||
|
||||
func (suite *UserValidateTestSuite) TestValidateUserOnlyUnconfirmedEmailOK() {
|
||||
// user has only UnconfirmedEmail and ConfirmedAt is not set
|
||||
u := happyUser()
|
||||
u.Email = ""
|
||||
u.UnconfirmedEmail = "whatever@example.org"
|
||||
u.ConfirmedAt = time.Time{}
|
||||
|
||||
err := validate.Struct(u)
|
||||
suite.NoError(err)
|
||||
}
|
||||
|
||||
func (suite *UserValidateTestSuite) TestValidateUserNoConfirmedAt() {
|
||||
// user has Email but no ConfirmedAt
|
||||
u := happyUser()
|
||||
u.ConfirmedAt = time.Time{}
|
||||
|
||||
err := validate.Struct(u)
|
||||
suite.EqualError(err, "Key: 'User.ConfirmedAt' Error:Field validation for 'ConfirmedAt' failed on the 'required_with' tag")
|
||||
}
|
||||
|
||||
func (suite *UserValidateTestSuite) TestValidateUserUnlikelySignInCount() {
|
||||
// user has Email but no ConfirmedAt
|
||||
u := happyUser()
|
||||
u.SignInCount = -69
|
||||
|
||||
err := validate.Struct(u)
|
||||
suite.EqualError(err, "Key: 'User.SignInCount' Error:Field validation for 'SignInCount' failed on the 'min' tag")
|
||||
}
|
||||
|
||||
func TestUserValidateTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(UserValidateTestSuite))
|
||||
}
|
|
@ -25,6 +25,7 @@ import (
|
|||
"github.com/stretchr/testify/suite"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/ap"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
)
|
||||
|
||||
|
@ -115,7 +116,7 @@ func (suite *StatusStatusHomeTimelineableTestSuite) TestThread() {
|
|||
Content: "nbnbdy expects dog",
|
||||
CreatedAt: testrig.TimeMustParse("2021-09-20T12:41:37+02:00"),
|
||||
UpdatedAt: testrig.TimeMustParse("2021-09-20T12:41:37+02:00"),
|
||||
Local: testrig.FalseBool(),
|
||||
Local: util.Ptr(false),
|
||||
AccountURI: "http://localhost:8080/users/the_mighty_zork",
|
||||
AccountID: threadParentAccount.ID,
|
||||
InReplyToID: originalStatus.ID,
|
||||
|
@ -124,13 +125,13 @@ func (suite *StatusStatusHomeTimelineableTestSuite) TestThread() {
|
|||
BoostOfID: "",
|
||||
ContentWarning: "",
|
||||
Visibility: gtsmodel.VisibilityFollowersOnly,
|
||||
Sensitive: testrig.FalseBool(),
|
||||
Sensitive: util.Ptr(false),
|
||||
Language: "en",
|
||||
CreatedWithApplicationID: "",
|
||||
Federated: testrig.TrueBool(),
|
||||
Boostable: testrig.TrueBool(),
|
||||
Replyable: testrig.TrueBool(),
|
||||
Likeable: testrig.TrueBool(),
|
||||
Federated: util.Ptr(true),
|
||||
Boostable: util.Ptr(true),
|
||||
Replyable: util.Ptr(true),
|
||||
Likeable: util.Ptr(true),
|
||||
ActivityStreamsType: ap.ObjectNote,
|
||||
}
|
||||
if err := suite.db.PutStatus(ctx, firstReplyStatus); err != nil {
|
||||
|
@ -168,7 +169,7 @@ func (suite *StatusStatusHomeTimelineableTestSuite) TestChainReplyFollowersOnly(
|
|||
Content: "didn't expect dog",
|
||||
CreatedAt: testrig.TimeMustParse("2021-09-20T12:40:37+02:00"),
|
||||
UpdatedAt: testrig.TimeMustParse("2021-09-20T12:40:37+02:00"),
|
||||
Local: testrig.FalseBool(),
|
||||
Local: util.Ptr(false),
|
||||
AccountURI: "http://fossbros-anonymous.io/users/foss_satan",
|
||||
AccountID: originalStatusParent.ID,
|
||||
InReplyToID: "",
|
||||
|
@ -177,13 +178,13 @@ func (suite *StatusStatusHomeTimelineableTestSuite) TestChainReplyFollowersOnly(
|
|||
BoostOfID: "",
|
||||
ContentWarning: "",
|
||||
Visibility: gtsmodel.VisibilityFollowersOnly,
|
||||
Sensitive: testrig.FalseBool(),
|
||||
Sensitive: util.Ptr(false),
|
||||
Language: "en",
|
||||
CreatedWithApplicationID: "",
|
||||
Federated: testrig.TrueBool(),
|
||||
Boostable: testrig.TrueBool(),
|
||||
Replyable: testrig.TrueBool(),
|
||||
Likeable: testrig.TrueBool(),
|
||||
Federated: util.Ptr(true),
|
||||
Boostable: util.Ptr(true),
|
||||
Replyable: util.Ptr(true),
|
||||
Likeable: util.Ptr(true),
|
||||
ActivityStreamsType: ap.ObjectNote,
|
||||
}
|
||||
if err := suite.db.PutStatus(ctx, originalStatus); err != nil {
|
||||
|
@ -202,7 +203,7 @@ func (suite *StatusStatusHomeTimelineableTestSuite) TestChainReplyFollowersOnly(
|
|||
Content: "nbnbdy expects dog",
|
||||
CreatedAt: testrig.TimeMustParse("2021-09-20T12:41:37+02:00"),
|
||||
UpdatedAt: testrig.TimeMustParse("2021-09-20T12:41:37+02:00"),
|
||||
Local: testrig.FalseBool(),
|
||||
Local: util.Ptr(false),
|
||||
AccountURI: "http://localhost:8080/users/the_mighty_zork",
|
||||
AccountID: replyingAccount.ID,
|
||||
InReplyToID: originalStatus.ID,
|
||||
|
@ -211,13 +212,13 @@ func (suite *StatusStatusHomeTimelineableTestSuite) TestChainReplyFollowersOnly(
|
|||
BoostOfID: "",
|
||||
ContentWarning: "",
|
||||
Visibility: gtsmodel.VisibilityFollowersOnly,
|
||||
Sensitive: testrig.FalseBool(),
|
||||
Sensitive: util.Ptr(false),
|
||||
Language: "en",
|
||||
CreatedWithApplicationID: "",
|
||||
Federated: testrig.TrueBool(),
|
||||
Boostable: testrig.TrueBool(),
|
||||
Replyable: testrig.TrueBool(),
|
||||
Likeable: testrig.TrueBool(),
|
||||
Federated: util.Ptr(true),
|
||||
Boostable: util.Ptr(true),
|
||||
Replyable: util.Ptr(true),
|
||||
Likeable: util.Ptr(true),
|
||||
ActivityStreamsType: ap.ObjectNote,
|
||||
}
|
||||
if err := suite.db.PutStatus(ctx, firstReplyStatus); err != nil {
|
||||
|
@ -236,7 +237,7 @@ func (suite *StatusStatusHomeTimelineableTestSuite) TestChainReplyFollowersOnly(
|
|||
Content: "*nobody",
|
||||
CreatedAt: testrig.TimeMustParse("2021-09-20T12:42:37+02:00"),
|
||||
UpdatedAt: testrig.TimeMustParse("2021-09-20T12:42:37+02:00"),
|
||||
Local: testrig.FalseBool(),
|
||||
Local: util.Ptr(false),
|
||||
AccountURI: "http://localhost:8080/users/the_mighty_zork",
|
||||
AccountID: replyingAccount.ID,
|
||||
InReplyToID: firstReplyStatus.ID,
|
||||
|
@ -245,13 +246,13 @@ func (suite *StatusStatusHomeTimelineableTestSuite) TestChainReplyFollowersOnly(
|
|||
BoostOfID: "",
|
||||
ContentWarning: "",
|
||||
Visibility: gtsmodel.VisibilityFollowersOnly,
|
||||
Sensitive: testrig.FalseBool(),
|
||||
Sensitive: util.Ptr(false),
|
||||
Language: "en",
|
||||
CreatedWithApplicationID: "",
|
||||
Federated: testrig.TrueBool(),
|
||||
Boostable: testrig.TrueBool(),
|
||||
Replyable: testrig.TrueBool(),
|
||||
Likeable: testrig.TrueBool(),
|
||||
Federated: util.Ptr(true),
|
||||
Boostable: util.Ptr(true),
|
||||
Replyable: util.Ptr(true),
|
||||
Likeable: util.Ptr(true),
|
||||
ActivityStreamsType: ap.ObjectNote,
|
||||
}
|
||||
if err := suite.db.PutStatus(ctx, secondReplyStatus); err != nil {
|
||||
|
@ -281,7 +282,7 @@ func (suite *StatusStatusHomeTimelineableTestSuite) TestChainReplyPublicAndUnloc
|
|||
Content: "didn't expect dog",
|
||||
CreatedAt: testrig.TimeMustParse("2021-09-20T12:40:37+02:00"),
|
||||
UpdatedAt: testrig.TimeMustParse("2021-09-20T12:40:37+02:00"),
|
||||
Local: testrig.FalseBool(),
|
||||
Local: util.Ptr(false),
|
||||
AccountURI: "http://fossbros-anonymous.io/users/foss_satan",
|
||||
AccountID: originalStatusParent.ID,
|
||||
InReplyToID: "",
|
||||
|
@ -290,13 +291,13 @@ func (suite *StatusStatusHomeTimelineableTestSuite) TestChainReplyPublicAndUnloc
|
|||
BoostOfID: "",
|
||||
ContentWarning: "",
|
||||
Visibility: gtsmodel.VisibilityUnlocked,
|
||||
Sensitive: testrig.FalseBool(),
|
||||
Sensitive: util.Ptr(false),
|
||||
Language: "en",
|
||||
CreatedWithApplicationID: "",
|
||||
Federated: testrig.TrueBool(),
|
||||
Boostable: testrig.TrueBool(),
|
||||
Replyable: testrig.TrueBool(),
|
||||
Likeable: testrig.TrueBool(),
|
||||
Federated: util.Ptr(true),
|
||||
Boostable: util.Ptr(true),
|
||||
Replyable: util.Ptr(true),
|
||||
Likeable: util.Ptr(true),
|
||||
ActivityStreamsType: ap.ObjectNote,
|
||||
}
|
||||
if err := suite.db.PutStatus(ctx, originalStatus); err != nil {
|
||||
|
@ -315,7 +316,7 @@ func (suite *StatusStatusHomeTimelineableTestSuite) TestChainReplyPublicAndUnloc
|
|||
Content: "nbnbdy expects dog",
|
||||
CreatedAt: testrig.TimeMustParse("2021-09-20T12:41:37+02:00"),
|
||||
UpdatedAt: testrig.TimeMustParse("2021-09-20T12:41:37+02:00"),
|
||||
Local: testrig.FalseBool(),
|
||||
Local: util.Ptr(false),
|
||||
AccountURI: "http://localhost:8080/users/the_mighty_zork",
|
||||
AccountID: replyingAccount.ID,
|
||||
InReplyToID: originalStatus.ID,
|
||||
|
@ -324,13 +325,13 @@ func (suite *StatusStatusHomeTimelineableTestSuite) TestChainReplyPublicAndUnloc
|
|||
BoostOfID: "",
|
||||
ContentWarning: "",
|
||||
Visibility: gtsmodel.VisibilityPublic,
|
||||
Sensitive: testrig.FalseBool(),
|
||||
Sensitive: util.Ptr(false),
|
||||
Language: "en",
|
||||
CreatedWithApplicationID: "",
|
||||
Federated: testrig.TrueBool(),
|
||||
Boostable: testrig.TrueBool(),
|
||||
Replyable: testrig.TrueBool(),
|
||||
Likeable: testrig.TrueBool(),
|
||||
Federated: util.Ptr(true),
|
||||
Boostable: util.Ptr(true),
|
||||
Replyable: util.Ptr(true),
|
||||
Likeable: util.Ptr(true),
|
||||
ActivityStreamsType: ap.ObjectNote,
|
||||
}
|
||||
if err := suite.db.PutStatus(ctx, firstReplyStatus); err != nil {
|
||||
|
@ -349,7 +350,7 @@ func (suite *StatusStatusHomeTimelineableTestSuite) TestChainReplyPublicAndUnloc
|
|||
Content: "*nobody",
|
||||
CreatedAt: testrig.TimeMustParse("2021-09-20T12:42:37+02:00"),
|
||||
UpdatedAt: testrig.TimeMustParse("2021-09-20T12:42:37+02:00"),
|
||||
Local: testrig.FalseBool(),
|
||||
Local: util.Ptr(false),
|
||||
AccountURI: "http://localhost:8080/users/the_mighty_zork",
|
||||
AccountID: replyingAccount.ID,
|
||||
InReplyToID: firstReplyStatus.ID,
|
||||
|
@ -358,13 +359,13 @@ func (suite *StatusStatusHomeTimelineableTestSuite) TestChainReplyPublicAndUnloc
|
|||
BoostOfID: "",
|
||||
ContentWarning: "",
|
||||
Visibility: gtsmodel.VisibilityUnlocked,
|
||||
Sensitive: testrig.FalseBool(),
|
||||
Sensitive: util.Ptr(false),
|
||||
Language: "en",
|
||||
CreatedWithApplicationID: "",
|
||||
Federated: testrig.TrueBool(),
|
||||
Boostable: testrig.TrueBool(),
|
||||
Replyable: testrig.TrueBool(),
|
||||
Likeable: testrig.TrueBool(),
|
||||
Federated: util.Ptr(true),
|
||||
Boostable: util.Ptr(true),
|
||||
Replyable: util.Ptr(true),
|
||||
Likeable: util.Ptr(true),
|
||||
ActivityStreamsType: ap.ObjectNote,
|
||||
}
|
||||
if err := suite.db.PutStatus(ctx, secondReplyStatus); err != nil {
|
||||
|
|
|
@ -27,29 +27,43 @@ const (
|
|||
robotsPath = "/robots.txt"
|
||||
robotsMetaAllowSome = "nofollow, noarchive, nositelinkssearchbox, max-image-preview:standard" // https://developers.google.com/search/docs/crawling-indexing/robots-meta-tag#robotsmeta
|
||||
robotsTxt = `# GoToSocial robots.txt -- to edit, see internal/web/robots.go
|
||||
# more info @ https://developers.google.com/search/docs/crawling-indexing/robots/intro
|
||||
# More info @ https://developers.google.com/search/docs/crawling-indexing/robots/intro
|
||||
|
||||
# Before we commence, a giant fuck you to ChatGPT in particular.
|
||||
# https://platform.openai.com/docs/gptbot
|
||||
User-agent: GPTBot
|
||||
Disallow: /
|
||||
|
||||
# Rules for everything else.
|
||||
User-agent: *
|
||||
Crawl-delay: 500
|
||||
# api stuff
|
||||
|
||||
# API endpoints.
|
||||
Disallow: /api/
|
||||
# auth/login stuff
|
||||
|
||||
# Auth/login endpoints.
|
||||
Disallow: /auth/
|
||||
Disallow: /oauth/
|
||||
Disallow: /check_your_email
|
||||
Disallow: /wait_for_approval
|
||||
Disallow: /account_disabled
|
||||
# well known stuff
|
||||
|
||||
# Well-known endpoints.
|
||||
Disallow: /.well-known/
|
||||
# files
|
||||
|
||||
# Fileserver/media.
|
||||
Disallow: /fileserver/
|
||||
# s2s AP stuff
|
||||
|
||||
# Fedi S2S API endpoints.
|
||||
Disallow: /users/
|
||||
Disallow: /emoji/
|
||||
# panels
|
||||
|
||||
# Settings panels.
|
||||
Disallow: /admin
|
||||
Disallow: /user
|
||||
Disallow: /settings/
|
||||
# domain blocklist
|
||||
|
||||
# Domain blocklist.
|
||||
Disallow: /about/suspended`
|
||||
)
|
||||
|
||||
|
|
|
@ -2,6 +2,11 @@
|
|||
|
||||
set -e
|
||||
|
||||
# Ensure test args are set.
|
||||
ARGS=${@}; [ -z "$ARGS" ] && \
|
||||
ARGS='./...'
|
||||
|
||||
# Database config.
|
||||
DB_NAME='postgres'
|
||||
DB_USER='postgres'
|
||||
DB_PASS='postgres'
|
||||
|
@ -34,4 +39,4 @@ GTS_DB_PORT=${DB_PORT} \
|
|||
GTS_DB_USER=${DB_USER} \
|
||||
GTS_DB_PASSWORD=${DB_PASS} \
|
||||
GTS_DB_DATABASE=${DB_NAME} \
|
||||
go test ./... -p 1 ${@}
|
||||
go test ./... -p 1 ${ARGS}
|
|
@ -2,6 +2,11 @@
|
|||
|
||||
set -e
|
||||
|
||||
# Ensure test args are set.
|
||||
ARGS=${@}; [ -z "$ARGS" ] && \
|
||||
ARGS='./...'
|
||||
|
||||
# Run the SQLite tests.
|
||||
GTS_DB_TYPE=sqlite \
|
||||
GTS_DB_ADDRESS=':memory:' \
|
||||
go test ./... ${@}
|
||||
go test ${ARGS}
|
File diff suppressed because it is too large
Load diff
36
vendor/codeberg.org/gruf/go-cache/v3/result/cache.go
generated
vendored
36
vendor/codeberg.org/gruf/go-cache/v3/result/cache.go
generated
vendored
|
@ -91,6 +91,7 @@ func (c *Cache[T]) SetEvictionCallback(hook func(T)) {
|
|||
|
||||
if res.Error != nil {
|
||||
// Skip value hooks
|
||||
putResult(res)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -119,6 +120,7 @@ func (c *Cache[T]) SetInvalidateCallback(hook func(T)) {
|
|||
|
||||
if res.Error != nil {
|
||||
// Skip value hooks
|
||||
putResult(res)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -147,10 +149,8 @@ func (c *Cache[T]) IgnoreErrors(ignore func(error) bool) {
|
|||
|
||||
// Load will attempt to load an existing result from the cacche for the given lookup and key parts, else calling the provided load function and caching the result.
|
||||
func (c *Cache[T]) Load(lookup string, load func() (T, error), keyParts ...any) (T, error) {
|
||||
var (
|
||||
zero T
|
||||
res *result
|
||||
)
|
||||
var zero T
|
||||
var res *result
|
||||
|
||||
// Get lookup key info by name.
|
||||
keyInfo := c.lookups.get(lookup)
|
||||
|
@ -164,12 +164,11 @@ func (c *Cache[T]) Load(lookup string, load func() (T, error), keyParts ...any)
|
|||
// Acquire cache lock
|
||||
c.cache.Lock()
|
||||
|
||||
// Look for primary cache key
|
||||
pkeys := keyInfo.pkeys[ckey]
|
||||
|
||||
if len(pkeys) > 0 {
|
||||
// Look for primary key for cache key (only accept len=1)
|
||||
if pkeys := keyInfo.pkeys[ckey]; len(pkeys) == 1 {
|
||||
// Fetch the result for primary key
|
||||
entry, ok := c.cache.Cache.Get(pkeys[0])
|
||||
|
||||
if ok {
|
||||
// Since the invalidation / eviction hooks acquire a mutex
|
||||
// lock separately, and only at this point are the pkeys
|
||||
|
@ -298,12 +297,11 @@ func (c *Cache[T]) Has(lookup string, keyParts ...any) bool {
|
|||
// Acquire cache lock
|
||||
c.cache.Lock()
|
||||
|
||||
// Look for primary key for cache key
|
||||
pkeys := keyInfo.pkeys[ckey]
|
||||
|
||||
if len(pkeys) > 0 {
|
||||
// Look for primary key for cache key (only accept len=1)
|
||||
if pkeys := keyInfo.pkeys[ckey]; len(pkeys) == 1 {
|
||||
// Fetch the result for primary key
|
||||
entry, ok := c.cache.Cache.Get(pkeys[0])
|
||||
|
||||
if ok {
|
||||
// Since the invalidation / eviction hooks acquire a mutex
|
||||
// lock separately, and only at this point are the pkeys
|
||||
|
@ -364,17 +362,25 @@ func (c *Cache[T]) store(res *result) (evict func()) {
|
|||
if key.info.unique && len(pkeys) > 0 {
|
||||
for _, conflict := range pkeys {
|
||||
// Get the overlapping result with this key.
|
||||
entry, _ := c.cache.Cache.Get(conflict)
|
||||
confRes := entry.Value.(*result)
|
||||
entry, ok := c.cache.Cache.Get(conflict)
|
||||
|
||||
if !ok {
|
||||
// Since the invalidation / eviction hooks acquire a mutex
|
||||
// lock separately, and only at this point are the pkeys
|
||||
// updated, there is a chance that a primary key may return
|
||||
// no matching entry. Hence we have to check for it here.
|
||||
continue
|
||||
}
|
||||
|
||||
// From conflicting entry, drop this key, this
|
||||
// will prevent eviction cleanup key confusion.
|
||||
confRes := entry.Value.(*result)
|
||||
confRes.Keys.drop(key.info.name)
|
||||
|
||||
if len(res.Keys) == 0 {
|
||||
// We just over-wrote the only lookup key for
|
||||
// this value, so we drop its primary key too.
|
||||
c.cache.Cache.Delete(conflict)
|
||||
_ = c.cache.Cache.Delete(conflict)
|
||||
|
||||
// Add finished result to evict queue.
|
||||
toEvict = append(toEvict, confRes)
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue