Compare commits

...

16 commits

Author SHA1 Message Date
Rafael Caricio b3194f1fee
Add missing published field to Announce 2023-08-10 22:15:17 +02:00
Rafael Caricio af29c06290
Hack: Allow for me specifically to migrate to GoToSocial 2023-08-08 20:01:45 +02:00
kim 0ddc2edf19
[bugfix] only set content-length AFTER rewinding body bytes (#2086) 2023-08-08 12:45:29 +01:00
kim 3920bc87d1
[bugfix] don't accept unrelated statuses (#2078)
Co-authored-by: Daenney <daenney@users.noreply.github.com>
Co-authored-by: tsmethurst <tobi.smethurst@protonmail.com>
2023-08-08 12:26:34 +01:00
tobi 4b05dcde43
[chore] Update robots.txt, give chatgpt the middle finger (#2085) 2023-08-08 13:16:34 +02:00
Daenney 9df4d38c43
[chore] Add Feditext as recommended client (#2081)
With Feditext now accepting beta users, this adds it as the third
client to recommend so we have web and the dominant mobile platforms
covered.

This also removes the screenshots from the README, because it became a
mess trying to add a third one. Either the cells become very narrow, or
the table doubles in height. As the UI may also change over time, it
might be better to point folks at the apps instead who'll hopefully have
up to date screenshots in their storefronts.
2023-08-08 12:19:41 +02:00
Daenney be3718f6e4
[chore] Use generic pointer function (#2080)
This replaces the different $TypePtr functions with a generic
implementation.
2023-08-07 18:38:11 +01:00
dependabot[bot] 517829ae6a
[chore]: Bump github.com/tdewolff/minify/v2 from 2.12.7 to 2.12.8 (#2073) 2023-08-07 08:28:49 +00:00
Vyr Cossont 0f812746b7
[feature] Allow full BCP 47 in language inputs (#2067)
* Allow full BCP 47 in language inputs

Fixes #2066

* Fuse validation and normalization for languages

* Remove outdated comment line

* Move post language canonicalization test
2023-08-07 10:25:54 +02:00
dependabot[bot] 303a6a6b1d
[chore]: Bump golang.org/x/oauth2 from 0.10.0 to 0.11.0 (#2076) 2023-08-07 08:21:44 +00:00
dependabot[bot] cc69250bbe
[chore]: Bump github.com/abema/go-mp4 from 0.11.0 to 0.12.0 (#2075) 2023-08-07 08:18:28 +00:00
dependabot[bot] 0242f03d36
[chore]: Bump golang.org/x/crypto from 0.11.0 to 0.12.0 (#2077) 2023-08-07 08:13:55 +00:00
dependabot[bot] aaa5985d7d
[chore]: Bump golang.org/x/image from 0.9.0 to 0.11.0 (#2074) 2023-08-07 08:03:43 +00:00
Daenney c1375ca5c1
[chore] Remove go-playground/validator (#2069)
* [chore] Remove go-playground/validator

It turns out we're not actually using the validator code. This is a
remnant from when we intended to use it, but the presence of it and its
struct tags creates the illusion we're validating a lot of things we're
not. It resulted in some confusion when we were trying to figure out
language valdiation.

Remove all this code, so that only the validation functions from the
validate package we actually use remain. I'm not touching the struct
tags in the migrations in order to avoid things potentially thinking
migrations need to be re-run.

* [chore] Bring back a struct tag on api

The validate on internal/api is Gin doing form validation, not the
validator from go-playground/validator.
2023-08-06 12:22:40 +02:00
kim 6f4ae8f58d
[bugfix] update cache library with nil ptr fix (#2070)
Signed-off-by: kim <grufwub@gmail.com>
2023-08-06 11:55:04 +02:00
Max Rosin 7f1f2b80ea
[docs] fix postgres create database command (#2071)
The current command `create database gotosocial with locale C.UTF-8 template template0;` fails because the locale has to be quoted:
```
postgres=# create database gotosocial with locale C.UTF-8 template template0;
ERROR:  syntax error at or near "."
LINE 1: create database gotosocial with locale C.UTF-8 template temp...
```

Tested manually that it works with the quotes and the official postgres examples also use quotes around the locales: https://www.postgresql.org/docs/current/sql-createdatabase.html
2023-08-06 11:54:07 +02:00
168 changed files with 3058 additions and 4926 deletions

View file

@ -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
:-----------------------------------------------------------:|:------------------------------------------------------------------:
![An image of GoToSocial in Tusky](./docs/assets/tusky.png) | ![An image of GoToSocial in Semaphore](./docs/assets/semaphore.png)
* [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

View file

@ -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;
```

View file

@ -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?

View file

@ -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

View file

@ -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
View file

@ -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
View file

@ -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=

View file

@ -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"}
},

View file

@ -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())
}

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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

View file

@ -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))
}

View file

@ -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 {

View file

@ -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() +

View file

@ -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!

View file

@ -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 := &gtsmodel.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 := &gtsmodel.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())
}

View file

@ -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 {

View file

@ -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 := &gtsmodel.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())
}

View file

@ -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)

View file

@ -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,
}
}

View file

@ -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)

View file

@ -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
*/

View file

@ -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)

View file

@ -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.

View file

@ -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.
}

View file

@ -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

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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?
}

View file

@ -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
}

View file

@ -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"`
}

View file

@ -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
}

View file

@ -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?
}

View file

@ -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?
}

View file

@ -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
}

View file

@ -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.

View file

@ -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.

View file

@ -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
}

View file

@ -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.
}

View file

@ -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.

View file

@ -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
}

View file

@ -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"`
}

View file

@ -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.

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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.
}

View file

@ -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
}

View file

@ -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.
}

View file

@ -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)
}

View file

@ -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.

View file

@ -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,

View file

@ -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",

View file

@ -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())
}

View file

@ -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 {

View file

@ -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)

View file

@ -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 := &gtsmodel.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,
}

View file

@ -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 {

View file

@ -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)

View file

@ -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)

View file

@ -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,
}

View file

@ -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))
}

View file

@ -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
View 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
}

View file

@ -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 &gtsmodel.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))
}

View file

@ -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 &gtsmodel.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))
}

View file

@ -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 &gtsmodel.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))
}

View file

@ -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 &gtsmodel.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))
}

View file

@ -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 &gtsmodel.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))
}

View file

@ -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 &gtsmodel.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))
}

View file

@ -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 &gtsmodel.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))
}

View file

@ -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 &gtsmodel.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))
}

View file

@ -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 &gtsmodel.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))
}

View file

@ -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

View file

@ -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)
}
})
}
}

View file

@ -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 &gtsmodel.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))
}

View file

@ -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 &gtsmodel.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))
}

View file

@ -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 &gtsmodel.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))
}

View file

@ -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 &gtsmodel.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))
}

View file

@ -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 &gtsmodel.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))
}

View file

@ -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 &gtsmodel.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))
}

View file

@ -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 &gtsmodel.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))
}

View file

@ -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 &gtsmodel.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))
}

View file

@ -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 &gtsmodel.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))
}

View file

@ -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
}

View file

@ -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 := &gtsmodel.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))
}

View file

@ -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 &gtsmodel.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))
}

View file

@ -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 &gtsmodel.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))
}

View file

@ -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 {

View file

@ -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`
)

View file

@ -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}

View file

@ -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

View file

@ -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