From 2786b5f88742c3f32b8ed497df3737cb4f57caec Mon Sep 17 00:00:00 2001 From: tsmethurst Date: Tue, 31 Aug 2021 15:59:12 +0200 Subject: change muchos things --- internal/processing/processor.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'internal/processing/processor.go') diff --git a/internal/processing/processor.go b/internal/processing/processor.go index 8df464ce0..1ade38564 100644 --- a/internal/processing/processor.go +++ b/internal/processing/processor.go @@ -32,6 +32,7 @@ import ( "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/media" + "github.com/superseriousbusiness/gotosocial/internal/messages" "github.com/superseriousbusiness/gotosocial/internal/oauth" "github.com/superseriousbusiness/gotosocial/internal/processing/account" "github.com/superseriousbusiness/gotosocial/internal/processing/admin" @@ -219,8 +220,8 @@ type Processor interface { // processor just implements the Processor interface type processor struct { - fromClientAPI chan gtsmodel.FromClientAPI - fromFederator chan gtsmodel.FromFederator + fromClientAPI chan messages.FromClientAPI + fromFederator chan messages.FromFederator federator federation.Federator stop chan interface{} log *logrus.Logger @@ -247,8 +248,8 @@ type processor struct { // NewProcessor returns a new Processor that uses the given federator and logger func NewProcessor(config *config.Config, tc typeutils.TypeConverter, federator federation.Federator, oauthServer oauth.Server, mediaHandler media.Handler, storage blob.Storage, timelineManager timeline.Manager, db db.DB, log *logrus.Logger) Processor { - fromClientAPI := make(chan gtsmodel.FromClientAPI, 1000) - fromFederator := make(chan gtsmodel.FromFederator, 1000) + fromClientAPI := make(chan messages.FromClientAPI, 1000) + fromFederator := make(chan messages.FromFederator, 1000) statusProcessor := status.New(db, tc, config, fromClientAPI, log) streamingProcessor := streaming.New(db, tc, oauthServer, config, log) -- cgit v1.2.3 From 4696e1a7b389599fa981f334b343daa911b11f5d Mon Sep 17 00:00:00 2001 From: tsmethurst Date: Wed, 1 Sep 2021 18:29:25 +0200 Subject: moving stuff around --- internal/api/client/account/accountcreate.go | 12 +- internal/api/client/admin/emojicreate.go | 4 +- internal/api/client/auth/auth_test.go | 1 - internal/api/client/auth/callback.go | 6 +- internal/api/client/status/statuscreate.go | 4 +- internal/api/client/streaming/stream.go | 8 +- internal/cliactions/admin/account/account.go | 20 +- internal/db/bundb/bundb.go | 3 + .../migrations/20210816411877_struct_validation.go | 83 ------ .../20210816411877_struct_validation/account.go | 78 ------ .../application.go | 32 --- .../20210816411877_struct_validation/block.go | 15 -- .../domainblock.go | 35 --- .../emaildomainblock.go | 31 --- .../20210816411877_struct_validation/emoji.go | 45 ---- .../20210816411877_struct_validation/follow.go | 35 --- .../followrequest.go | 35 --- .../20210816411877_struct_validation/instance.go | 25 -- .../mediaattachment.go | 117 --------- .../20210816411877_struct_validation/mention.go | 59 ----- .../notification.go | 49 ---- .../routersession.go | 26 -- .../20210816411877_struct_validation/status.go | 116 --------- .../statusbookmark.go | 33 --- .../20210816411877_struct_validation/statusfave.go | 34 --- .../20210816411877_struct_validation/statusmute.go | 33 --- .../20210816411877_struct_validation/tag.go | 34 --- .../20210816411877_struct_validation/user.go | 70 ----- internal/db/bundb/migrations/README.md | 51 +++- internal/gtsmodel/account.go | 35 ++- internal/gtsmodel/application.go | 19 +- internal/gtsmodel/block.go | 34 ++- internal/gtsmodel/block_test.go | 115 --------- internal/gtsmodel/client.go | 32 ++- internal/gtsmodel/domainblock.go | 20 +- internal/gtsmodel/domainblock_test.go | 121 --------- internal/gtsmodel/emaildomainblock.go | 12 +- internal/gtsmodel/emaildomainblock_test.go | 96 ------- internal/gtsmodel/emoji.go | 6 +- internal/gtsmodel/emoji_test.go | 194 -------------- internal/gtsmodel/follow.go | 20 +- internal/gtsmodel/follow_test.go | 87 ------- internal/gtsmodel/followrequest.go | 20 +- internal/gtsmodel/followrequest_test.go | 87 ------- internal/gtsmodel/instance.go | 24 +- internal/gtsmodel/instance_test.go | 145 ----------- internal/gtsmodel/mediaattachment.go | 24 +- internal/gtsmodel/mediaattachment_test.go | 229 ----------------- internal/gtsmodel/mention.go | 22 +- internal/gtsmodel/mention_test.go | 101 -------- internal/gtsmodel/notification.go | 5 +- internal/gtsmodel/notification_test.go | 97 ------- internal/gtsmodel/poll.go | 19 -- internal/gtsmodel/relationship.go | 36 --- internal/gtsmodel/routersession.go | 10 +- internal/gtsmodel/routersession_test.go | 87 ------- internal/gtsmodel/status.go | 12 +- internal/gtsmodel/status_test.go | 162 ------------ internal/gtsmodel/statusbookmark.go | 17 +- internal/gtsmodel/statusbookmark_test.go | 87 ------- internal/gtsmodel/statusfave.go | 19 +- internal/gtsmodel/statusfave_test.go | 100 -------- internal/gtsmodel/statusmute.go | 17 +- internal/gtsmodel/statusmute_test.go | 87 ------- internal/gtsmodel/stream.go | 38 --- internal/gtsmodel/tag.go | 18 +- internal/gtsmodel/tag_test.go | 92 ------- internal/gtsmodel/token.go | 43 ++-- internal/gtsmodel/user.go | 71 +++--- internal/gtsmodel/user_test.go | 124 --------- internal/gtsmodel/validate.go | 75 ------ internal/gtsmodel/validate_test.go | 64 ----- internal/oauth/clientstore_test.go | 4 +- internal/processing/account/update.go | 10 +- internal/processing/app.go | 2 - internal/processing/instance.go | 12 +- internal/processing/processor.go | 3 +- internal/processing/streaming.go | 3 +- internal/processing/streaming/openstream.go | 19 +- internal/processing/streaming/streamdelete.go | 16 +- internal/processing/streaming/streaming.go | 3 +- .../processing/streaming/streamnotification.go | 17 +- internal/processing/streaming/streamstatus.go | 17 +- internal/regexes/regexes.go | 136 ++++++++++ internal/stream/stream.go | 38 +++ internal/text/common.go | 4 +- internal/transport/derefinstance.go | 3 +- internal/typeutils/internaltofrontend.go | 1 - internal/util/regexes.go | 114 --------- internal/util/statustools.go | 12 +- internal/util/uri.go | 42 +-- internal/util/validation.go | 178 ------------- internal/util/validation_test.go | 283 --------------------- internal/validate/block_test.go | 116 +++++++++ internal/validate/domainblock_test.go | 122 +++++++++ internal/validate/emaildomainblock_test.go | 97 +++++++ internal/validate/emoji_test.go | 195 ++++++++++++++ internal/validate/follow_test.go | 88 +++++++ internal/validate/followrequest_test.go | 88 +++++++ internal/validate/formvalidation.go | 182 +++++++++++++ internal/validate/formvalidation_test.go | 283 +++++++++++++++++++++ internal/validate/instance_test.go | 146 +++++++++++ internal/validate/mediaattachment_test.go | 230 +++++++++++++++++ internal/validate/mention_test.go | 102 ++++++++ internal/validate/notification_test.go | 98 +++++++ internal/validate/routersession_test.go | 88 +++++++ internal/validate/status_test.go | 163 ++++++++++++ internal/validate/statusbookmark_test.go | 88 +++++++ internal/validate/statusfave_test.go | 101 ++++++++ internal/validate/statusmute_test.go | 88 +++++++ internal/validate/structvalidation.go | 75 ++++++ internal/validate/structvalidation_test.go | 65 +++++ internal/validate/tag_test.go | 93 +++++++ internal/validate/user_test.go | 125 +++++++++ testrig/testmodels.go | 3 - 115 files changed, 3249 insertions(+), 4121 deletions(-) delete mode 100644 internal/db/bundb/migrations/20210816411877_struct_validation.go delete mode 100644 internal/db/bundb/migrations/20210816411877_struct_validation/account.go delete mode 100644 internal/db/bundb/migrations/20210816411877_struct_validation/application.go delete mode 100644 internal/db/bundb/migrations/20210816411877_struct_validation/block.go delete mode 100644 internal/db/bundb/migrations/20210816411877_struct_validation/domainblock.go delete mode 100644 internal/db/bundb/migrations/20210816411877_struct_validation/emaildomainblock.go delete mode 100644 internal/db/bundb/migrations/20210816411877_struct_validation/emoji.go delete mode 100644 internal/db/bundb/migrations/20210816411877_struct_validation/follow.go delete mode 100644 internal/db/bundb/migrations/20210816411877_struct_validation/followrequest.go delete mode 100644 internal/db/bundb/migrations/20210816411877_struct_validation/instance.go delete mode 100644 internal/db/bundb/migrations/20210816411877_struct_validation/mediaattachment.go delete mode 100644 internal/db/bundb/migrations/20210816411877_struct_validation/mention.go delete mode 100644 internal/db/bundb/migrations/20210816411877_struct_validation/notification.go delete mode 100644 internal/db/bundb/migrations/20210816411877_struct_validation/routersession.go delete mode 100644 internal/db/bundb/migrations/20210816411877_struct_validation/status.go delete mode 100644 internal/db/bundb/migrations/20210816411877_struct_validation/statusbookmark.go delete mode 100644 internal/db/bundb/migrations/20210816411877_struct_validation/statusfave.go delete mode 100644 internal/db/bundb/migrations/20210816411877_struct_validation/statusmute.go delete mode 100644 internal/db/bundb/migrations/20210816411877_struct_validation/tag.go delete mode 100644 internal/db/bundb/migrations/20210816411877_struct_validation/user.go delete mode 100644 internal/gtsmodel/block_test.go delete mode 100644 internal/gtsmodel/domainblock_test.go delete mode 100644 internal/gtsmodel/emaildomainblock_test.go delete mode 100644 internal/gtsmodel/emoji_test.go delete mode 100644 internal/gtsmodel/follow_test.go delete mode 100644 internal/gtsmodel/followrequest_test.go delete mode 100644 internal/gtsmodel/instance_test.go delete mode 100644 internal/gtsmodel/mediaattachment_test.go delete mode 100644 internal/gtsmodel/mention_test.go delete mode 100644 internal/gtsmodel/notification_test.go delete mode 100644 internal/gtsmodel/poll.go delete mode 100644 internal/gtsmodel/relationship.go delete mode 100644 internal/gtsmodel/routersession_test.go delete mode 100644 internal/gtsmodel/status_test.go delete mode 100644 internal/gtsmodel/statusbookmark_test.go delete mode 100644 internal/gtsmodel/statusfave_test.go delete mode 100644 internal/gtsmodel/statusmute_test.go delete mode 100644 internal/gtsmodel/stream.go delete mode 100644 internal/gtsmodel/tag_test.go delete mode 100644 internal/gtsmodel/user_test.go delete mode 100644 internal/gtsmodel/validate.go delete mode 100644 internal/gtsmodel/validate_test.go create mode 100644 internal/regexes/regexes.go create mode 100644 internal/stream/stream.go delete mode 100644 internal/util/regexes.go delete mode 100644 internal/util/validation.go delete mode 100644 internal/util/validation_test.go create mode 100644 internal/validate/block_test.go create mode 100644 internal/validate/domainblock_test.go create mode 100644 internal/validate/emaildomainblock_test.go create mode 100644 internal/validate/emoji_test.go create mode 100644 internal/validate/follow_test.go create mode 100644 internal/validate/followrequest_test.go create mode 100644 internal/validate/formvalidation.go create mode 100644 internal/validate/formvalidation_test.go create mode 100644 internal/validate/instance_test.go create mode 100644 internal/validate/mediaattachment_test.go create mode 100644 internal/validate/mention_test.go create mode 100644 internal/validate/notification_test.go create mode 100644 internal/validate/routersession_test.go create mode 100644 internal/validate/status_test.go create mode 100644 internal/validate/statusbookmark_test.go create mode 100644 internal/validate/statusfave_test.go create mode 100644 internal/validate/statusmute_test.go create mode 100644 internal/validate/structvalidation.go create mode 100644 internal/validate/structvalidation_test.go create mode 100644 internal/validate/tag_test.go create mode 100644 internal/validate/user_test.go (limited to 'internal/processing/processor.go') diff --git a/internal/api/client/account/accountcreate.go b/internal/api/client/account/accountcreate.go index a9d672f80..3fab1488f 100644 --- a/internal/api/client/account/accountcreate.go +++ b/internal/api/client/account/accountcreate.go @@ -27,7 +27,7 @@ import ( "github.com/superseriousbusiness/gotosocial/internal/api/model" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/oauth" - "github.com/superseriousbusiness/gotosocial/internal/util" + "github.com/superseriousbusiness/gotosocial/internal/validate" ) // AccountCreatePOSTHandler swagger:operation POST /api/v1/accounts accountCreate @@ -118,15 +118,15 @@ func validateCreateAccount(form *model.AccountCreateRequest, c *config.AccountsC return errors.New("registration is not open for this server") } - if err := util.ValidateUsername(form.Username); err != nil { + if err := validate.Username(form.Username); err != nil { return err } - if err := util.ValidateEmail(form.Email); err != nil { + if err := validate.Email(form.Email); err != nil { return err } - if err := util.ValidateNewPassword(form.Password); err != nil { + if err := validate.NewPassword(form.Password); err != nil { return err } @@ -134,11 +134,11 @@ func validateCreateAccount(form *model.AccountCreateRequest, c *config.AccountsC return errors.New("agreement to terms and conditions not given") } - if err := util.ValidateLanguage(form.Locale); err != nil { + if err := validate.Language(form.Locale); err != nil { return err } - if err := util.ValidateSignUpReason(form.Reason, c.ReasonRequired); err != nil { + if err := validate.SignUpReason(form.Reason, c.ReasonRequired); err != nil { return err } diff --git a/internal/api/client/admin/emojicreate.go b/internal/api/client/admin/emojicreate.go index 859933b16..019298976 100644 --- a/internal/api/client/admin/emojicreate.go +++ b/internal/api/client/admin/emojicreate.go @@ -28,7 +28,7 @@ import ( "github.com/superseriousbusiness/gotosocial/internal/api/model" "github.com/superseriousbusiness/gotosocial/internal/media" "github.com/superseriousbusiness/gotosocial/internal/oauth" - "github.com/superseriousbusiness/gotosocial/internal/util" + "github.com/superseriousbusiness/gotosocial/internal/validate" ) // emojiCreateRequest swagger:operation POST /api/v1/admin/custom_emojis emojiCreate @@ -132,5 +132,5 @@ func validateCreateEmoji(form *model.EmojiCreateRequest) error { return fmt.Errorf("file size limit exceeded: limit is %d bytes but emoji was %d bytes", media.EmojiMaxBytes, form.Image.Size) } - return util.ValidateEmojiShortcode(form.Shortcode) + return validate.EmojiShortcode(form.Shortcode) } diff --git a/internal/api/client/auth/auth_test.go b/internal/api/client/auth/auth_test.go index 9b778ad1d..295c0e964 100644 --- a/internal/api/client/auth/auth_test.go +++ b/internal/api/client/auth/auth_test.go @@ -95,7 +95,6 @@ func (suite *AuthTestSuite) SetupSuite() { ClientID: "a-known-client-id", ClientSecret: "some-secret", Scopes: "read", - VapidKey: uuid.NewString(), } } diff --git a/internal/api/client/auth/callback.go b/internal/api/client/auth/callback.go index c2fbfb486..322ba5fc9 100644 --- a/internal/api/client/auth/callback.go +++ b/internal/api/client/auth/callback.go @@ -33,7 +33,7 @@ import ( "github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/oidc" - "github.com/superseriousbusiness/gotosocial/internal/util" + "github.com/superseriousbusiness/gotosocial/internal/validate" ) // CallbackGETHandler parses a token from an external auth provider. @@ -153,7 +153,7 @@ func (m *Module) parseUserFromClaims(ctx context.Context, claims *oidc.Claims, i } // check if we can just use claims.Name as-is - err = util.ValidateUsername(claims.Name) + err = validate.Username(claims.Name) if err == nil { // the name we have on the claims is already a valid username username = claims.Name @@ -166,7 +166,7 @@ func (m *Module) parseUserFromClaims(ctx context.Context, claims *oidc.Claims, i // lowercase the whole thing lower := strings.ToLower(underscored) // see if this is valid.... - if err := util.ValidateUsername(lower); err == nil { + if err := validate.Username(lower); err == nil { // we managed to get a valid username username = lower } else { diff --git a/internal/api/client/status/statuscreate.go b/internal/api/client/status/statuscreate.go index 09fc47b5b..c8ea019d2 100644 --- a/internal/api/client/status/statuscreate.go +++ b/internal/api/client/status/statuscreate.go @@ -27,7 +27,7 @@ import ( "github.com/superseriousbusiness/gotosocial/internal/api/model" "github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/oauth" - "github.com/superseriousbusiness/gotosocial/internal/util" + "github.com/superseriousbusiness/gotosocial/internal/validate" ) // StatusCreatePOSTHandler swagger:operation POST /api/v1/statuses statusCreate @@ -157,7 +157,7 @@ func validateCreateStatus(form *model.AdvancedStatusCreateForm, config *config.S // validate post language if form.Language != "" { - if err := util.ValidateLanguage(form.Language); err != nil { + if err := validate.Language(form.Language); err != nil { return err } } diff --git a/internal/api/client/streaming/stream.go b/internal/api/client/streaming/stream.go index fa210e8d8..4a9dcfe52 100644 --- a/internal/api/client/streaming/stream.go +++ b/internal/api/client/streaming/stream.go @@ -146,13 +146,13 @@ func (m *Module) StreamGETHandler(c *gin.Context) { } defer conn.Close() // whatever happens, when we leave this function we want to close the websocket connection - // inform the processor that we have a new connection and want a stream for it - stream, errWithCode := m.processor.OpenStreamForAccount(c.Request.Context(), account, streamType) + // inform the processor that we have a new connection and want a s for it + s, errWithCode := m.processor.OpenStreamForAccount(c.Request.Context(), account, streamType) if errWithCode != nil { c.JSON(errWithCode.Code(), errWithCode.Safe()) return } - defer close(stream.Hangup) // closing stream.Hangup indicates that we've finished with the connection (the client has gone), so we want to do this on exiting this handler + defer close(s.Hangup) // closing stream.Hangup indicates that we've finished with the connection (the client has gone), so we want to do this on exiting this handler // spawn a new ticker for pinging the connection periodically t := time.NewTicker(30 * time.Second) @@ -161,7 +161,7 @@ func (m *Module) StreamGETHandler(c *gin.Context) { sendLoop: for { select { - case m := <-stream.Messages: + case m := <-s.Messages: // we've got a streaming message!! l.Trace("received message from stream") if err := conn.WriteJSON(m); err != nil { diff --git a/internal/cliactions/admin/account/account.go b/internal/cliactions/admin/account/account.go index 46998ec6a..369f2b800 100644 --- a/internal/cliactions/admin/account/account.go +++ b/internal/cliactions/admin/account/account.go @@ -30,7 +30,7 @@ import ( "github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/db/bundb" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" - "github.com/superseriousbusiness/gotosocial/internal/util" + "github.com/superseriousbusiness/gotosocial/internal/validate" "golang.org/x/crypto/bcrypt" ) @@ -45,7 +45,7 @@ var Create cliactions.GTSAction = func(ctx context.Context, c *config.Config, lo if !ok { return errors.New("no username set") } - if err := util.ValidateUsername(username); err != nil { + if err := validate.Username(username); err != nil { return err } @@ -53,7 +53,7 @@ var Create cliactions.GTSAction = func(ctx context.Context, c *config.Config, lo if !ok { return errors.New("no email set") } - if err := util.ValidateEmail(email); err != nil { + if err := validate.Email(email); err != nil { return err } @@ -61,7 +61,7 @@ var Create cliactions.GTSAction = func(ctx context.Context, c *config.Config, lo if !ok { return errors.New("no password set") } - if err := util.ValidateNewPassword(password); err != nil { + if err := validate.NewPassword(password); err != nil { return err } @@ -84,7 +84,7 @@ var Confirm cliactions.GTSAction = func(ctx context.Context, c *config.Config, l if !ok { return errors.New("no username set") } - if err := util.ValidateUsername(username); err != nil { + if err := validate.Username(username); err != nil { return err } @@ -119,7 +119,7 @@ var Promote cliactions.GTSAction = func(ctx context.Context, c *config.Config, l if !ok { return errors.New("no username set") } - if err := util.ValidateUsername(username); err != nil { + if err := validate.Username(username); err != nil { return err } @@ -151,7 +151,7 @@ var Demote cliactions.GTSAction = func(ctx context.Context, c *config.Config, lo if !ok { return errors.New("no username set") } - if err := util.ValidateUsername(username); err != nil { + if err := validate.Username(username); err != nil { return err } @@ -183,7 +183,7 @@ var Disable cliactions.GTSAction = func(ctx context.Context, c *config.Config, l if !ok { return errors.New("no username set") } - if err := util.ValidateUsername(username); err != nil { + if err := validate.Username(username); err != nil { return err } @@ -221,7 +221,7 @@ var Password cliactions.GTSAction = func(ctx context.Context, c *config.Config, if !ok { return errors.New("no username set") } - if err := util.ValidateUsername(username); err != nil { + if err := validate.Username(username); err != nil { return err } @@ -229,7 +229,7 @@ var Password cliactions.GTSAction = func(ctx context.Context, c *config.Config, if !ok { return errors.New("no password set") } - if err := util.ValidateNewPassword(password); err != nil { + if err := validate.NewPassword(password); err != nil { return err } diff --git a/internal/db/bundb/bundb.go b/internal/db/bundb/bundb.go index f2e83887d..7ddcab5c7 100644 --- a/internal/db/bundb/bundb.go +++ b/internal/db/bundb/bundb.go @@ -86,6 +86,9 @@ func doMigration(ctx context.Context, db *bun.DB, log *logrus.Logger) error { group, err := migrator.Migrate(ctx) if err != nil { + if err.Error() == "migrate: there are no any migrations" { + return nil + } return err } diff --git a/internal/db/bundb/migrations/20210816411877_struct_validation.go b/internal/db/bundb/migrations/20210816411877_struct_validation.go deleted file mode 100644 index 15d2d7659..000000000 --- a/internal/db/bundb/migrations/20210816411877_struct_validation.go +++ /dev/null @@ -1,83 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package migrations - -import ( - "context" - - gtsmodel "github.com/superseriousbusiness/gotosocial/internal/db/bundb/migrations/20210816411877_struct_validation" - "github.com/uptrace/bun" -) - -func init() { - - var models []interface{} = []interface{}{ - >smodel.Account{}, - >smodel.Application{}, - >smodel.Block{}, - >smodel.DomainBlock{}, - >smodel.EmailDomainBlock{}, - >smodel.Follow{}, - >smodel.FollowRequest{}, - >smodel.MediaAttachment{}, - >smodel.Mention{}, - >smodel.Status{}, - >smodel.StatusToEmoji{}, - >smodel.StatusToTag{}, - >smodel.StatusFave{}, - >smodel.StatusBookmark{}, - >smodel.StatusMute{}, - >smodel.Tag{}, - >smodel.User{}, - >smodel.Emoji{}, - >smodel.Instance{}, - >smodel.Notification{}, - >smodel.RouterSession{}, - >smodel.Token{}, - >smodel.Client{}, - } - - up := func(ctx context.Context, db *bun.DB) error { - return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error { - for _, m := range models { - _, err := tx.NewCreateTable().Model(m).IfNotExists().Exec(ctx) - if err != nil { - return err - } - } - - return nil - }) - } - - down := func(ctx context.Context, db *bun.DB) error { - return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error { - _, err := tx.NewDropTable().Model(>smodel.Account{}).Exec(ctx) - if err != nil { - return err - } - - return nil - }) - } - - if err := Migrations.Register(up, down); err != nil { - panic(err) - } -} diff --git a/internal/db/bundb/migrations/20210816411877_struct_validation/account.go b/internal/db/bundb/migrations/20210816411877_struct_validation/account.go deleted file mode 100644 index 6c957b5c0..000000000 --- a/internal/db/bundb/migrations/20210816411877_struct_validation/account.go +++ /dev/null @@ -1,78 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package gtsmodel - -import ( - "crypto/rsa" - "time" -) - -// 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:",nullzero,notnull,default:current_timestamp"` // when was item created - UpdatedAt time.Time `validate:"-" bun:",nullzero,notnull,default:current_timestamp"` // when was item last updated - Username string `validate:"required" bun:",nullzero,notnull,unique:userdomain"` // 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:userdomain"` // Domain of the account, will be null if this is a local account, otherwise something like ``example.org`` or ``mastodon.social``. Should be unique with username. - 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:",nullzero"` // DisplayName for this account. Can be empty, then just the Username will be used for display purposes. - Fields []Field `validate:"-"` // a key/value map of fields that this account has added to their profile - Note string `validate:"-" bun:",nullzero"` // A note that this account has on their profile (ie., the account's bio/description of themselves) - Memorial bool `validate:"-" bun:",nullzero,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 - MovedToAccountID string `validate:"omitempty,ulid" bun:"type:CHAR(26),nullzero"` // This account has moved this account id in the database - Bot bool `validate:"-" bun:",nullzero,default:false"` // Does this account identify itself as a bot? - Reason string `validate:"-" bun:",nullzero"` // What reason was given for signing up when this account was created? - Locked bool `validate:"-" bun:",nullzero,default:true"` // Does this account need an approval for new followers? - Discoverable bool `validate:"-" bun:",nullzero,default:false"` // Should this account be shown in the instance's profile directory? - Privacy Visibility `validate:"oneof=public unlocked followers_only mutuals_only direct" bun:",nullzero,notnull,default:'public'"` // Default post privacy for this account - Sensitive bool `validate:"-" bun:",nullzero,default:false"` // Set posts from this account to sensitive by default? - Language string `validate:"-" bun:",nullzero,notnull,default:'en'"` // What language does this account post in? - URI string `validate:"required,url" bun:",nullzero,notnull,unique"` // ActivityPub URI for this account. - URL string `validate:"omitempty,url" bun:",unique,nullzero"` // Web URL for this account's profile - LastWebfingeredAt time.Time `validate:"-" bun:",nullzero,notnull,default:current_timestamp"` // Last time this account was refreshed/located with webfinger. - InboxURI string `validate:"omitempty,url" bun:",nullzero,unique"` // Address of this account's ActivityPub inbox, for sending activity to - OutboxURI string `validate:"omitempty,url" bun:",nullzero,unique"` // Address of this account's activitypub outbox - FollowingURI string `validate:"omitempty,url" bun:",nullzero,unique"` // URI for getting the following list of this account - FollowersURI string `validate:"omitempty,url" bun:",nullzero,unique"` // URI for getting the followers list of this account - FeaturedCollectionURI string `validate:"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"` // Privatekey for validating activitypub requests, will only be defined for local accounts - PublicKey *rsa.PublicKey `validate:"required"` // Publickey for encoding activitypub requests, will be defined for both local and remote accounts - PublicKeyURI string `validate:"required" bun:",nullzero,notnull"` // Web-reachable location of this account's public key - SensitizedAt time.Time `validate:"-" bun:",nullzero"` // When was this account set to have all its media shown as sensitive? - SilencedAt time.Time `validate:"-" bun:",nullzero"` // When was this account silenced (eg., statuses only visible to followers, not public)? - SuspendedAt time.Time `validate:"-" bun:",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:",nullzero,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 -} - -// 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). -} diff --git a/internal/db/bundb/migrations/20210816411877_struct_validation/application.go b/internal/db/bundb/migrations/20210816411877_struct_validation/application.go deleted file mode 100644 index 0791aae6a..000000000 --- a/internal/db/bundb/migrations/20210816411877_struct_validation/application.go +++ /dev/null @@ -1,32 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package gtsmodel - -// 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"` // id of this application in the db - Name string `validate:"required" bun:",nullzero,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" bun:",nullzero,notnull"` // redirect uri requested by the application for oauth2 flow - ClientID string `validate:"omitempty,ulid" bun:"type:CHAR(26),nullzero"` // 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:",nullzero,default:'read'"` // scopes requested when this app was created - VapidKey string `validate:"-" bun:",nullzero"` // a vapid key generated for this app when it was created -} diff --git a/internal/db/bundb/migrations/20210816411877_struct_validation/block.go b/internal/db/bundb/migrations/20210816411877_struct_validation/block.go deleted file mode 100644 index 61595c12d..000000000 --- a/internal/db/bundb/migrations/20210816411877_struct_validation/block.go +++ /dev/null @@ -1,15 +0,0 @@ -package gtsmodel - -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:",nullzero,notnull,default:current_timestamp"` // when was item created - UpdatedAt time.Time `validate:"-" bun:",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"` // 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"` // Who is the target of this block ? - TargetAccount *Account `validate:"-" bun:"rel:belongs-to"` // Account corresponding to targetAccountID -} diff --git a/internal/db/bundb/migrations/20210816411877_struct_validation/domainblock.go b/internal/db/bundb/migrations/20210816411877_struct_validation/domainblock.go deleted file mode 100644 index dd05ef0c6..000000000 --- a/internal/db/bundb/migrations/20210816411877_struct_validation/domainblock.go +++ /dev/null @@ -1,35 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package gtsmodel - -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:",nullzero,notnull,default:current_timestamp"` // when was item created - UpdatedAt time.Time `validate:"-" bun:",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:",nullzero"` // Private comment on this block, viewable to admins - PublicComment string `validate:"-" bun:",nullzero"` // Public comment on this block, viewable (optionally) by everyone - Obfuscate bool `validate:"-" bun:",nullzero,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? -} diff --git a/internal/db/bundb/migrations/20210816411877_struct_validation/emaildomainblock.go b/internal/db/bundb/migrations/20210816411877_struct_validation/emaildomainblock.go deleted file mode 100644 index 38f4a9580..000000000 --- a/internal/db/bundb/migrations/20210816411877_struct_validation/emaildomainblock.go +++ /dev/null @@ -1,31 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package gtsmodel - -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:",nullzero,notnull,default:current_timestamp"` // when was item created - UpdatedAt time.Time `validate:"-" bun:",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 -} diff --git a/internal/db/bundb/migrations/20210816411877_struct_validation/emoji.go b/internal/db/bundb/migrations/20210816411877_struct_validation/emoji.go deleted file mode 100644 index 71287130a..000000000 --- a/internal/db/bundb/migrations/20210816411877_struct_validation/emoji.go +++ /dev/null @@ -1,45 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package gtsmodel - -import "time" - -// Emoji represents a custom emoji that's been uploaded through the admin UI, and is useable by instance denizens. -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:",nullzero,notnull,default:current_timestamp"` // when was item created - UpdatedAt time.Time `validate:"-" bun:",nullzero,notnull,default:current_timestamp"` // when was item last updated - Shortcode string `validate:"required" bun:",notnull,unique:shortcodedomain"` // String shortcode for this emoji -- the part that's between colons. This should be lowercase a-z_ eg., 'blob_hug' 'purple_heart' Must be unique with domain. - Domain string `validate:"omitempty,fqdn" bun:",notnull,default:'',unique:shortcodedomain"` // 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:",nullzero,notnull,default:current_timestamp"` // When was the emoji image last updated? - Disabled bool `validate:"-" bun:",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:",notnull,default:true"` // Is this emoji visible in the admin emoji picker? - CategoryID string `validate:"omitempty,ulid" bun:"type:CHAR(26),nullzero"` // In which emoji category is this emoji visible? -} diff --git a/internal/db/bundb/migrations/20210816411877_struct_validation/follow.go b/internal/db/bundb/migrations/20210816411877_struct_validation/follow.go deleted file mode 100644 index c2b2633b9..000000000 --- a/internal/db/bundb/migrations/20210816411877_struct_validation/follow.go +++ /dev/null @@ -1,35 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package gtsmodel - -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:",nullzero,notnull,default:current_timestamp"` // when was item created - UpdatedAt time.Time `validate:"-" bun:",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"` // 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"` // Who is the target of this follow ? - TargetAccount *Account `validate:"-" bun:"rel:belongs-to"` // Account corresponding to targetAccountID - ShowReblogs bool `validate:"-" bun:",nullzero,default:true"` // Does this follow also want to see reblogs and not just posts? - Notify bool `validate:"-" bun:",nullzero,default:false"` // does the following account want to be notified when the followed account posts? -} diff --git a/internal/db/bundb/migrations/20210816411877_struct_validation/followrequest.go b/internal/db/bundb/migrations/20210816411877_struct_validation/followrequest.go deleted file mode 100644 index ae22f6487..000000000 --- a/internal/db/bundb/migrations/20210816411877_struct_validation/followrequest.go +++ /dev/null @@ -1,35 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package gtsmodel - -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:",nullzero,notnull,default:current_timestamp"` // when was item created - UpdatedAt time.Time `validate:"-" bun:",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"` // 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"` // Who is the target of this follow request? - TargetAccount *Account `validate:"-" bun:"rel:belongs-to"` // Account corresponding to targetAccountID - ShowReblogs bool `validate:"-" bun:",nullzero,default:true"` // Does this follow also want to see reblogs and not just posts? - Notify bool `validate:"-" bun:",nullzero,default:false"` // does the following account want to be notified when the followed account posts? -} diff --git a/internal/db/bundb/migrations/20210816411877_struct_validation/instance.go b/internal/db/bundb/migrations/20210816411877_struct_validation/instance.go deleted file mode 100644 index 4d36dbba8..000000000 --- a/internal/db/bundb/migrations/20210816411877_struct_validation/instance.go +++ /dev/null @@ -1,25 +0,0 @@ -package gtsmodel - -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:",nullzero,notnull,default:current_timestamp"` // when was item created - UpdatedAt time.Time `validate:"-" bun:",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:",nullzero"` // 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:",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:",nullzero"` // Short description of this instance - Description string `validate:"-" bun:",nullzero"` // Longer description of this instance - Terms string `validate:"-" bun:",nullzero"` // Terms and conditions of this instance - ContactEmail string `validate:"omitempty,email" bun:",nullzero"` // 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 -} diff --git a/internal/db/bundb/migrations/20210816411877_struct_validation/mediaattachment.go b/internal/db/bundb/migrations/20210816411877_struct_validation/mediaattachment.go deleted file mode 100644 index 53f226ad7..000000000 --- a/internal/db/bundb/migrations/20210816411877_struct_validation/mediaattachment.go +++ /dev/null @@ -1,117 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package gtsmodel - -import ( - "time" -) - -// 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:",nullzero,notnull,default:current_timestamp"` // when was item created - UpdatedAt time.Time `validate:"-" bun:",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 Gif Audio Video Unknown" bun:",notnull"` // Type of file (image/gif/audio/video) - FileMeta FileMeta `validate:"required" bun:",nullzero,notnull"` // Metadata about the file - AccountID string `validate:"required,ulid" bun:"type:CHAR(26),nullzero,notnull"` // To which account does this attachment belong - Account *Account `validate:"-" bun:"rel:has-one"` // Account corresponding to accountID - Description string `validate:"-" bun:",nullzero"` // 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:",notnull,nullzero"` // metadata for the whole file - Thumbnail Thumbnail `validate:"required" bun:",notnull,nullzero"` // small image thumbnail derived from a larger image, video, or audio file. - Avatar bool `validate:"-" bun:",notnull,default:false"` // Is this attachment being used as an avatar? - Header bool `validate:"-" bun:",notnull,default:false"` // Is this attachment being used as a header? -} - -// 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:",nullzero,notnull"` // File size in bytes - UpdatedAt time.Time `validate:"-" bun:",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:",nullzero,notnull"` // File size in bytes - UpdatedAt time.Time `validate:"-" bun:",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) -} - -// ProcessingStatus refers to how far along in the processing stage the attachment is. -type ProcessingStatus int - -// MediaAttachment processing states. -const ( - ProcessingStatusReceived ProcessingStatus = 0 // ProcessingStatusReceived indicates the attachment has been received and is awaiting processing. No thumbnail available yet. - ProcessingStatusProcessing ProcessingStatus = 1 // ProcessingStatusProcessing indicates the attachment is currently being processed. Thumbnail is available but full media is not. - ProcessingStatusProcessed ProcessingStatus = 2 // ProcessingStatusProcessed indicates the attachment has been fully processed and is ready to be served. - ProcessingStatusError ProcessingStatus = 666 // ProcessingStatusError indicates something went wrong processing the attachment and it won't be tried again--these can be deleted. -) - -// FileType refers to the file type of the media attaachment. -type FileType string - -// MediaAttachment file types. -const ( - FileTypeImage FileType = "Image" // FileTypeImage is for jpegs and pngs - FileTypeGif FileType = "Gif" // FileTypeGif is for native gifs and soundless videos that have been converted to gifs - FileTypeAudio FileType = "Audio" // FileTypeAudio is for audio-only files (no video) - FileTypeVideo FileType = "Video" // FileTypeVideo is for files with audio + visual - FileTypeUnknown FileType = "Unknown" // FileTypeUnknown is for unknown file types (surprise surprise!) -) - -// FileMeta describes metadata about the actual contents of the file. -type FileMeta struct { - Original Original `validate:"required"` - Small Small - Focus 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 float64 `validate:"required_with=Widhth Height Size"` // 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 float64 `validate:"required_with=Widhth Height Size"` // aspect ratio (width / height) -} - -// 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"` -} diff --git a/internal/db/bundb/migrations/20210816411877_struct_validation/mention.go b/internal/db/bundb/migrations/20210816411877_struct_validation/mention.go deleted file mode 100644 index d8359745d..000000000 --- a/internal/db/bundb/migrations/20210816411877_struct_validation/mention.go +++ /dev/null @@ -1,59 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package gtsmodel - -import "time" - -// 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:",nullzero,notnull,default:current_timestamp"` // when was item created - UpdatedAt time.Time `validate:"-" bun:",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:",notnull,default:false"` // Prevent this mention from generating a notification? - - /* - NON-DATABASE CONVENIENCE FIELDS - These fields are just for convenience while passing the mention - around internally, to make fewer database calls and whatnot. They're - not meant to be put in the database! - */ - - // NameString is for putting in the namestring of the mentioned user - // before the mention is dereferenced. Should be in a form along the lines of: - // @whatever_username@example.org - // - // This will not be put in the database, it's just for convenience. - NameString string `validate:"-" 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:"-"` - // 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:"-"` - // A pointer to the gtsmodel account of the mentioned account. -} diff --git a/internal/db/bundb/migrations/20210816411877_struct_validation/notification.go b/internal/db/bundb/migrations/20210816411877_struct_validation/notification.go deleted file mode 100644 index bb69bc8d4..000000000 --- a/internal/db/bundb/migrations/20210816411877_struct_validation/notification.go +++ /dev/null @@ -1,49 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package gtsmodel - -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:"ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database - CreatedAt time.Time `validate:"-" bun:",nullzero,notnull,default:current_timestamp"` // when was item created - 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"` // Which account does this notification target (ie., who will receive the notification?) - TargetAccount *Account `validate:"-" bun:"rel:belongs-to"` // Which account performed the action that created this notification? - 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:"rel:belongs-to"` // Account corresponding to originAccountID - 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:"rel:belongs-to"` // Status corresponding to statusID - Read bool `validate:"-" bun:",notnull,default:false"` // Notification has been seen/read -} - -// NotificationType describes the reason/type of this notification. -type NotificationType string - -// Notification Types -const ( - NotificationFollow NotificationType = "follow" // NotificationFollow -- someone followed you - NotificationFollowRequest NotificationType = "follow_request" // NotificationFollowRequest -- someone requested to follow you - NotificationMention NotificationType = "mention" // NotificationMention -- someone mentioned you in their status - NotificationReblog NotificationType = "reblog" // NotificationReblog -- someone boosted one of your statuses - NotificationFave NotificationType = "favourite" // NotificationFave -- someone faved/liked one of your statuses - NotificationPoll NotificationType = "poll" // NotificationPoll -- a poll you voted in or created has ended - NotificationStatus NotificationType = "status" // NotificationStatus -- someone you enabled notifications for has posted a status. -) diff --git a/internal/db/bundb/migrations/20210816411877_struct_validation/routersession.go b/internal/db/bundb/migrations/20210816411877_struct_validation/routersession.go deleted file mode 100644 index 374264fe4..000000000 --- a/internal/db/bundb/migrations/20210816411877_struct_validation/routersession.go +++ /dev/null @@ -1,26 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package gtsmodel - -// 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"` - Auth []byte `validate:"required,len=32" bun:"type:bytea,notnull,nullzero"` - Crypt []byte `validate:"required,len=32" bun:"type:bytea,notnull,nullzero"` -} diff --git a/internal/db/bundb/migrations/20210816411877_struct_validation/status.go b/internal/db/bundb/migrations/20210816411877_struct_validation/status.go deleted file mode 100644 index d81a45ec3..000000000 --- a/internal/db/bundb/migrations/20210816411877_struct_validation/status.go +++ /dev/null @@ -1,116 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package gtsmodel - -import ( - "time" -) - -// 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:",nullzero,notnull,default:current_timestamp"` // when was item created - UpdatedAt time.Time `validate:"-" bun:",nullzero,notnull,default:current_timestamp"` // when was item last updated - 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:",nullzero"` // content of this status; likely html-formatted but not guaranteed - AttachmentIDs []string `validate:"dive,ulid" bun:"attachments,array,nullzero"` // 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,nullzero"` // 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,nullzero"` // 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,nullzero"` // 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:",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:"-" bun:",nullzero,notnull"` // visibility entry for this status - Sensitive bool `validate:"-" bun:",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 - VisibilityAdvanced VisibilityAdvanced `validate:"required" bun:",nullzero,notnull" ` // advanced visibility for this status - 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:",nullzero"` // Original text of the status without formatting - Pinned bool `validate:"-" bun:",notnull,default:false" ` // Has this status been pinned by its owner? -} - -// 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"` -} - -// 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"` -} - -// Visibility represents the visibility granularity of a status. -type Visibility string - -const ( - // VisibilityPublic means this status will be visible to everyone on all timelines. - VisibilityPublic Visibility = "public" - // VisibilityUnlocked means this status will be visible to everyone, but will only show on home timeline to followers, and in lists. - VisibilityUnlocked Visibility = "unlocked" - // VisibilityFollowersOnly means this status is viewable to followers only. - VisibilityFollowersOnly Visibility = "followers_only" - // VisibilityMutualsOnly means this status is visible to mutual followers only. - VisibilityMutualsOnly Visibility = "mutuals_only" - // VisibilityDirect means this status is visible only to mentioned recipients. - VisibilityDirect Visibility = "direct" - // VisibilityDefault is used when no other setting can be found. - VisibilityDefault Visibility = VisibilityUnlocked -) - -// VisibilityAdvanced models flags for fine-tuning visibility and interactivity of a status. -// -// All flags default to true. -// -// If PUBLIC is selected, flags will all be overwritten to TRUE regardless of what is selected. -// -// If UNLOCKED is selected, any flags can be turned on or off in any combination. -// -// If FOLLOWERS-ONLY or MUTUALS-ONLY are selected, boostable will always be FALSE. Other flags can be turned on or off as desired. -// -// If DIRECT is selected, boostable will be FALSE, and all other flags will be TRUE. -type VisibilityAdvanced struct { - Federated bool `validate:"-" bun:",notnull,default:true"` // This status will be federated beyond the local timeline(s) - Boostable bool `validate:"-" bun:",notnull,default:true"` // This status can be boosted/reblogged - Replyable bool `validate:"-" bun:",notnull,default:true"` // This status can be replied to - Likeable bool `validate:"-" bun:",notnull,default:true"` // This status can be liked/faved -} diff --git a/internal/db/bundb/migrations/20210816411877_struct_validation/statusbookmark.go b/internal/db/bundb/migrations/20210816411877_struct_validation/statusbookmark.go deleted file mode 100644 index 76a2a866d..000000000 --- a/internal/db/bundb/migrations/20210816411877_struct_validation/statusbookmark.go +++ /dev/null @@ -1,33 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package gtsmodel - -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:",nullzero,notnull,default:current_timestamp"` // when was item created - 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 -} diff --git a/internal/db/bundb/migrations/20210816411877_struct_validation/statusfave.go b/internal/db/bundb/migrations/20210816411877_struct_validation/statusfave.go deleted file mode 100644 index 6647e941a..000000000 --- a/internal/db/bundb/migrations/20210816411877_struct_validation/statusfave.go +++ /dev/null @@ -1,34 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package gtsmodel - -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:",nullzero,notnull,default:current_timestamp"` // when was item created - AccountID string `validate:"required,ulid" bun:"type:CHAR(26),nullzero,notnull"` // id of the account that created ('did') the fave - Account *Account `validate:"-" bun:"rel:belongs-to"` // 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:"rel:belongs-to"` // account owning the faved status - StatusID string `validate:"required,ulid" bun:"type:CHAR(26),nullzero,notnull"` // database id of the status that has been 'faved' - Status *Status `validate:"-" bun:"rel:belongs-to"` // the faved status - URI string `validate:"required,url" bun:",nullzero,notnull"` // ActivityPub URI of this fave -} diff --git a/internal/db/bundb/migrations/20210816411877_struct_validation/statusmute.go b/internal/db/bundb/migrations/20210816411877_struct_validation/statusmute.go deleted file mode 100644 index 70789e557..000000000 --- a/internal/db/bundb/migrations/20210816411877_struct_validation/statusmute.go +++ /dev/null @@ -1,33 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package gtsmodel - -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:",nullzero,notnull,default:current_timestamp"` // when was item created - 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 -} diff --git a/internal/db/bundb/migrations/20210816411877_struct_validation/tag.go b/internal/db/bundb/migrations/20210816411877_struct_validation/tag.go deleted file mode 100644 index 14ff26f87..000000000 --- a/internal/db/bundb/migrations/20210816411877_struct_validation/tag.go +++ /dev/null @@ -1,34 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package gtsmodel - -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:",nullzero,notnull,default:current_timestamp"` // when was item created - UpdatedAt time.Time `validate:"-" bun:",nullzero,notnull,default:current_timestamp"` // when was item last updated - URL string `validate:"required,url" bun:",nullzero,notnull"` // Href/web address of this tag, eg https://example.org/tags/somehashtag - Name string `validate:"required" bun:",unique,nullzero,notnull"` // name of this tag -- the tag without the hash part - FirstSeenFromAccountID string `validate:"omitempty,ulid" bun:"type:CHAR(26),nullzero"` // Which account ID is the first one we saw using this tag? - Useable bool `validate:"-" bun:",notnull,default:true"` // can our instance users use this tag? - Listable bool `validate:"-" bun:",notnull,default:true"` // can our instance users look up this tag? - LastStatusAt time.Time `validate:"-" bun:",nullzero,notnull,default:current_timestamp"` // when was this tag last used? -} diff --git a/internal/db/bundb/migrations/20210816411877_struct_validation/user.go b/internal/db/bundb/migrations/20210816411877_struct_validation/user.go deleted file mode 100644 index e0568d6a0..000000000 --- a/internal/db/bundb/migrations/20210816411877_struct_validation/user.go +++ /dev/null @@ -1,70 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package gtsmodel - -import ( - "net" - "time" -) - -// 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:",nullzero,notnull,default:current_timestamp"` // when was item created - UpdatedAt time.Time `validate:"-" bun:",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:",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:",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:"-" bun:",nullzero,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:"required,ulid" bun:"type:CHAR(26),nullzero,notnull"` // 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:",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:",nullzero"` // When did we send email confirmation to this user? - ConfirmedAt time.Time `validate:"required_with=Email" bun:",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:",notnull,default:false"` // Is this user a moderator? - Admin bool `validate:"-" bun:",notnull,default:false"` // Is this user an admin? - Disabled bool `validate:"-" bun:",notnull,default:false"` // Is this user disabled from posting? - Approved bool `validate:"-" bun:",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:",nullzero"` // When did we email the user their reset-password email? - - EncryptedOTPSecret string `validate:"-" bun:",nullzero"` - EncryptedOTPSecretIv string `validate:"-" bun:",nullzero"` - EncryptedOTPSecretSalt string `validate:"-" bun:",nullzero"` - OTPRequiredForLogin bool `validate:"-" bun:",notnull,default:false"` - OTPBackupCodes []string `validate:"-" bun:",nullzero"` - ConsumedTimestamp int `validate:"-" bun:",nullzero"` - RememberToken string `validate:"-" bun:",nullzero"` - SignInToken string `validate:"-" bun:",nullzero"` - SignInTokenSentAt time.Time `validate:"-" bun:",nullzero"` - WebauthnID string `validate:"-" bun:",nullzero"` -} diff --git a/internal/db/bundb/migrations/README.md b/internal/db/bundb/migrations/README.md index e293d43c7..fee262936 100644 --- a/internal/db/bundb/migrations/README.md +++ b/internal/db/bundb/migrations/README.md @@ -4,7 +4,55 @@ [See here](https://bun.uptrace.dev/guide/migrations.html#migration-names) -As a template, take one of the existing migration files and modify it. It will be automatically loaded and handled by Bun. +As a template, take one of the existing migration files and modify it, or use the below code snippet: + +```go +/* + GoToSocial + Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package migrations + +import ( + "context" + + "github.com/uptrace/bun" +) + +func init() { + up := func(ctx context.Context, db *bun.DB) error { + return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error { + // your logic here + return nil + }) + } + + down := func(ctx context.Context, db *bun.DB) error { + return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error { + // your logic here + return nil + }) + } + + if err := Migrations.Register(up, down); err != nil { + panic(err) + } +} +``` ## File format @@ -19,3 +67,4 @@ echo "$(date --utc +%Y%m%H%M%S%N | head -c 14)_$(git rev-parse --abbrev-ref HEAD ## Rules of thumb 1. **DON'T DROP TABLES**!!!!!!!! +2. Don't make something `NOT NULL` if it's likely to already contain `null` fields. diff --git a/internal/gtsmodel/account.go b/internal/gtsmodel/account.go index 75ea02b4f..4f385b3a6 100644 --- a/internal/gtsmodel/account.go +++ b/internal/gtsmodel/account.go @@ -30,8 +30,8 @@ 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:",nullzero,notnull,default:current_timestamp"` // when was item created - UpdatedAt time.Time `validate:"-" bun:",nullzero,notnull,default:current_timestamp"` // when was item last updated + CreatedAt time.Time `validate:"-" bun:"type:timestamp,nullzero,notnull,default:current_timestamp"` // when was item created + UpdatedAt time.Time `validate:"-" bun:"type:timestamp,nullzero,notnull,default:current_timestamp"` // when was item last updated Username string `validate:"required" bun:",nullzero,notnull,unique:userdomain"` // 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:userdomain"` // Domain of the account, will be null if this is a local account, otherwise something like ``example.org`` or ``mastodon.social``. Should be unique with username. AvatarMediaAttachmentID string `validate:"omitempty,ulid" bun:"type:CHAR(26),nullzero"` // Database ID of the media attachment, if present @@ -54,20 +54,20 @@ type Account struct { Sensitive bool `validate:"-" bun:",nullzero,default:false"` // Set posts from this account to sensitive by default? Language string `validate:"-" bun:",nullzero,notnull,default:'en'"` // What language does this account post in? URI string `validate:"required,url" bun:",nullzero,notnull,unique"` // ActivityPub URI for this account. - URL string `validate:"omitempty,url" bun:",unique,nullzero"` // Web URL for this account's profile - LastWebfingeredAt time.Time `validate:"-" bun:",nullzero,notnull,default:current_timestamp"` // Last time this account was refreshed/located with webfinger. + URL string `validate:"omitempty,url" bun:",nullzero,unique"` // Web URL for this account's profile + LastWebfingeredAt time.Time `validate:"required_with=Domain" bun:"type:timestamp,nullzero"` // Last time this account was refreshed/located with webfinger. InboxURI string `validate:"omitempty,url" bun:",nullzero,unique"` // Address of this account's ActivityPub inbox, for sending activity to OutboxURI string `validate:"omitempty,url" bun:",nullzero,unique"` // Address of this account's activitypub outbox FollowingURI string `validate:"omitempty,url" bun:",nullzero,unique"` // URI for getting the following list of this account FollowersURI string `validate:"omitempty,url" bun:",nullzero,unique"` // URI for getting the followers list of this account FeaturedCollectionURI string `validate:"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? + 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"` // Privatekey for validating activitypub requests, will only be defined for local accounts PublicKey *rsa.PublicKey `validate:"required"` // Publickey for encoding activitypub requests, will be defined for both local and remote accounts - PublicKeyURI string `validate:"required" bun:",nullzero,notnull"` // Web-reachable location of this account's public key - SensitizedAt time.Time `validate:"-" bun:",nullzero"` // When was this account set to have all its media shown as sensitive? - SilencedAt time.Time `validate:"-" bun:",nullzero"` // When was this account silenced (eg., statuses only visible to followers, not public)? - SuspendedAt time.Time `validate:"-" bun:",nullzero"` // When was this account suspended (eg., don't allow it to log in/post, don't accept media/posts from this account) + PublicKeyURI string `validate:"required,url" bun:",nullzero,notnull,unique"` // Web-reachable location of this account's public key + SensitizedAt time.Time `validate:"-" bun:"type:timestamp,nullzero"` // When was this account set to have all its media shown as sensitive? + SilencedAt time.Time `validate:"-" bun:"type:timestamp,nullzero"` // When was this account silenced (eg., statuses only visible to followers, not public)? + SuspendedAt time.Time `validate:"-" bun:"type:timestamp,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:",nullzero,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 } @@ -80,3 +80,20 @@ type Field struct { Value string `validate:"required"` // Value of this field. VerifiedAt time.Time `validate:"-" bun:",nullzero"` // This field was verified at (optional). } + +// Relationship describes a requester's relationship with another account. +type Relationship struct { + ID string // The account id. + Following bool // Are you following this user? + ShowingReblogs bool // Are you receiving this user's boosts in your home timeline? + Notifying bool // Have you enabled notifications for this user? + FollowedBy bool // Are you followed by this user? + Blocking bool // Are you blocking this user? + BlockedBy bool // Is this user blocking you? + Muting bool // Are you muting this user? + MutingNotifications bool // Are you muting notifications from this user? + Requested bool // Do you have a pending follow request for this user? + DomainBlocking bool // Are you blocking this user's domain? + Endorsed bool // Are you featuring this user on your profile? + Note string // Your note on this account. +} diff --git a/internal/gtsmodel/application.go b/internal/gtsmodel/application.go index 0791aae6a..30035123e 100644 --- a/internal/gtsmodel/application.go +++ b/internal/gtsmodel/application.go @@ -18,15 +18,18 @@ package gtsmodel +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"` // id of this application in the db - Name string `validate:"required" bun:",nullzero,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" bun:",nullzero,notnull"` // redirect uri requested by the application for oauth2 flow - ClientID string `validate:"omitempty,ulid" bun:"type:CHAR(26),nullzero"` // 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:",nullzero,default:'read'"` // scopes requested when this app was created - VapidKey string `validate:"-" bun:",nullzero"` // a vapid key generated for this app when it was created + 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:timestamp,nullzero,notnull,default:current_timestamp"` // when was item created + UpdatedAt time.Time `validate:"-" bun:"type:timestamp,nullzero,notnull,default:current_timestamp"` // when was item last updated + Name string `validate:"required" bun:",nullzero,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" bun:",nullzero,notnull"` // redirect uri requested by the application for oauth2 flow + ClientID string `validate:"omitempty,ulid" bun:"type:CHAR(26),nullzero"` // 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:",nullzero,notnull,default:'read'"` // scopes requested when this app was created } diff --git a/internal/gtsmodel/block.go b/internal/gtsmodel/block.go index 61595c12d..2cc089e6b 100644 --- a/internal/gtsmodel/block.go +++ b/internal/gtsmodel/block.go @@ -1,15 +1,33 @@ +/* + GoToSocial + Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + package gtsmodel 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:",nullzero,notnull,default:current_timestamp"` // when was item created - UpdatedAt time.Time `validate:"-" bun:",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"` // 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"` // Who is the target of this block ? - TargetAccount *Account `validate:"-" bun:"rel:belongs-to"` // Account corresponding to targetAccountID + 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:timestamp,nullzero,notnull,default:current_timestamp"` // when was item created + UpdatedAt time.Time `validate:"-" bun:"type:timestamp,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"` // 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"` // Who is the target of this block ? + TargetAccount *Account `validate:"-" bun:"rel:belongs-to"` // Account corresponding to targetAccountID } diff --git a/internal/gtsmodel/block_test.go b/internal/gtsmodel/block_test.go deleted file mode 100644 index 307f26cd9..000000000 --- a/internal/gtsmodel/block_test.go +++ /dev/null @@ -1,115 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package gtsmodel_test - -import ( - "testing" - "time" - - "github.com/stretchr/testify/suite" - "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" -) - -func happyBlock() *gtsmodel.Block { - return >smodel.Block{ - ID: "01FE91RJR88PSEEE30EV35QR8N", - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - URI: "https://example.org/accounts/someone/blocks/01FE91RJR88PSEEE30EV35QR8N", - AccountID: "01FEED79PRMVWPRMFHFQM8MJQN", - Account: nil, - TargetAccountID: "01FEEDMF6C0QD589MRK7919Z0R", - TargetAccount: nil, - } -} - -type BlockValidateTestSuite struct { - suite.Suite -} - -func (suite *BlockValidateTestSuite) TestValidateBlockHappyPath() { - // no problem here - d := happyBlock() - err := gtsmodel.ValidateStruct(*d) - suite.NoError(err) -} - -func (suite *BlockValidateTestSuite) TestValidateBlockBadID() { - d := happyBlock() - - d.ID = "" - err := gtsmodel.ValidateStruct(*d) - suite.EqualError(err, "Key: 'Block.ID' Error:Field validation for 'ID' failed on the 'required' tag") - - d.ID = "01FE96W293ZPRG9FQQP48HK8N001FE96W32AT24VYBGM12WN3GKB" - err = gtsmodel.ValidateStruct(*d) - suite.EqualError(err, "Key: 'Block.ID' Error:Field validation for 'ID' failed on the 'ulid' tag") -} - -func (suite *BlockValidateTestSuite) TestValidateBlockNoCreatedAt() { - d := happyBlock() - - d.CreatedAt = time.Time{} - err := gtsmodel.ValidateStruct(*d) - suite.NoError(err) -} - -func (suite *BlockValidateTestSuite) TestValidateBlockCreatedByAccountID() { - d := happyBlock() - - d.AccountID = "" - err := gtsmodel.ValidateStruct(*d) - suite.EqualError(err, "Key: 'Block.AccountID' Error:Field validation for 'AccountID' failed on the 'required' tag") - - d.AccountID = "this-is-not-a-valid-ulid" - err = gtsmodel.ValidateStruct(*d) - suite.EqualError(err, "Key: 'Block.AccountID' Error:Field validation for 'AccountID' failed on the 'ulid' tag") -} - -func (suite *BlockValidateTestSuite) TestValidateBlockTargetAccountID() { - d := happyBlock() - - d.TargetAccountID = "invalid-ulid" - err := gtsmodel.ValidateStruct(*d) - suite.EqualError(err, "Key: 'Block.TargetAccountID' Error:Field validation for 'TargetAccountID' failed on the 'ulid' tag") - - d.TargetAccountID = "01FEEDHX4G7EGHF5GD9E82Y51Q" - err = gtsmodel.ValidateStruct(*d) - suite.NoError(err) - - d.TargetAccountID = "" - err = gtsmodel.ValidateStruct(*d) - suite.EqualError(err, "Key: 'Block.TargetAccountID' Error:Field validation for 'TargetAccountID' failed on the 'required' tag") -} - -func (suite *BlockValidateTestSuite) TestValidateBlockURI() { - d := happyBlock() - - d.URI = "invalid-uri" - err := gtsmodel.ValidateStruct(*d) - suite.EqualError(err, "Key: 'Block.URI' Error:Field validation for 'URI' failed on the 'url' tag") - - d.URI = "" - err = gtsmodel.ValidateStruct(*d) - 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)) -} diff --git a/internal/gtsmodel/client.go b/internal/gtsmodel/client.go index 24028fd69..de9bd569a 100644 --- a/internal/gtsmodel/client.go +++ b/internal/gtsmodel/client.go @@ -1,9 +1,31 @@ +/* + GoToSocial + Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + package gtsmodel -// Client is a handy little wrapper for typical oauth client details +import "time" + +// Client is a wrapper for OAuth client details. type Client struct { - ID string `bun:"type:CHAR(26),pk,notnull"` - Secret string - Domain string - UserID string + 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:timestamp,nullzero,notnull,default:current_timestamp"` // when was item created + UpdatedAt time.Time `validate:"-" bun:"type:timestamp,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" 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 } diff --git a/internal/gtsmodel/domainblock.go b/internal/gtsmodel/domainblock.go index dd05ef0c6..4c72b842a 100644 --- a/internal/gtsmodel/domainblock.go +++ b/internal/gtsmodel/domainblock.go @@ -22,14 +22,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:",nullzero,notnull,default:current_timestamp"` // when was item created - UpdatedAt time.Time `validate:"-" bun:",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:",nullzero"` // Private comment on this block, viewable to admins - PublicComment string `validate:"-" bun:",nullzero"` // Public comment on this block, viewable (optionally) by everyone - Obfuscate bool `validate:"-" bun:",nullzero,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 `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database + CreatedAt time.Time `validate:"-" bun:"type:timestamp,nullzero,notnull,default:current_timestamp"` // when was item created + UpdatedAt time.Time `validate:"-" bun:"type:timestamp,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:",nullzero"` // Private comment on this block, viewable to admins + PublicComment string `validate:"-" bun:",nullzero"` // Public comment on this block, viewable (optionally) by everyone + Obfuscate bool `validate:"-" bun:",nullzero,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? } diff --git a/internal/gtsmodel/domainblock_test.go b/internal/gtsmodel/domainblock_test.go deleted file mode 100644 index ced29ab31..000000000 --- a/internal/gtsmodel/domainblock_test.go +++ /dev/null @@ -1,121 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package gtsmodel_test - -import ( - "testing" - "time" - - "github.com/stretchr/testify/suite" - "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" -) - -func happyDomainBlock() *gtsmodel.DomainBlock { - return >smodel.DomainBlock{ - ID: "01FE91RJR88PSEEE30EV35QR8N", - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - Domain: "baddudes.suck", - CreatedByAccountID: "01FEED79PRMVWPRMFHFQM8MJQN", - PrivateComment: "we don't like em", - PublicComment: "poo poo dudes", - Obfuscate: false, - SubscriptionID: "", - } -} - -type DomainBlockValidateTestSuite struct { - suite.Suite -} - -func (suite *DomainBlockValidateTestSuite) TestValidateDomainBlockHappyPath() { - // no problem here - d := happyDomainBlock() - err := gtsmodel.ValidateStruct(*d) - suite.NoError(err) -} - -func (suite *DomainBlockValidateTestSuite) TestValidateDomainBlockBadID() { - d := happyDomainBlock() - - d.ID = "" - err := gtsmodel.ValidateStruct(*d) - suite.EqualError(err, "Key: 'DomainBlock.ID' Error:Field validation for 'ID' failed on the 'required' tag") - - d.ID = "01FE96W293ZPRG9FQQP48HK8N001FE96W32AT24VYBGM12WN3GKB" - err = gtsmodel.ValidateStruct(*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 := gtsmodel.ValidateStruct(*d) - suite.NoError(err) -} - -func (suite *DomainBlockValidateTestSuite) TestValidateDomainBlockBadDomain() { - d := happyDomainBlock() - - d.Domain = "" - err := gtsmodel.ValidateStruct(*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 = gtsmodel.ValidateStruct(*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 := gtsmodel.ValidateStruct(*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 = gtsmodel.ValidateStruct(*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 := gtsmodel.ValidateStruct(*d) - suite.NoError(err) -} - -func (suite *DomainBlockValidateTestSuite) TestValidateDomainSubscriptionID() { - d := happyDomainBlock() - - d.SubscriptionID = "invalid-ulid" - err := gtsmodel.ValidateStruct(*d) - suite.EqualError(err, "Key: 'DomainBlock.SubscriptionID' Error:Field validation for 'SubscriptionID' failed on the 'ulid' tag") - - d.SubscriptionID = "01FEEDHX4G7EGHF5GD9E82Y51Q" - err = gtsmodel.ValidateStruct(*d) - suite.NoError(err) -} - -func TestDomainBlockValidateTestSuite(t *testing.T) { - suite.Run(t, new(DomainBlockValidateTestSuite)) -} diff --git a/internal/gtsmodel/emaildomainblock.go b/internal/gtsmodel/emaildomainblock.go index 38f4a9580..2118068f2 100644 --- a/internal/gtsmodel/emaildomainblock.go +++ b/internal/gtsmodel/emaildomainblock.go @@ -22,10 +22,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:",nullzero,notnull,default:current_timestamp"` // when was item created - UpdatedAt time.Time `validate:"-" bun:",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 `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database + CreatedAt time.Time `validate:"-" bun:"type:timestamp,nullzero,notnull,default:current_timestamp"` // when was item created + UpdatedAt time.Time `validate:"-" bun:"type:timestamp,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 } diff --git a/internal/gtsmodel/emaildomainblock_test.go b/internal/gtsmodel/emaildomainblock_test.go deleted file mode 100644 index 83d7c4c6a..000000000 --- a/internal/gtsmodel/emaildomainblock_test.go +++ /dev/null @@ -1,96 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package gtsmodel_test - -import ( - "testing" - "time" - - "github.com/stretchr/testify/suite" - "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" -) - -func happyEmailDomainBlock() *gtsmodel.EmailDomainBlock { - return >smodel.EmailDomainBlock{ - ID: "01FE91RJR88PSEEE30EV35QR8N", - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - Domain: "baddudes.suck", - CreatedByAccountID: "01FEED79PRMVWPRMFHFQM8MJQN", - } -} - -type EmailDomainBlockValidateTestSuite struct { - suite.Suite -} - -func (suite *EmailDomainBlockValidateTestSuite) TestValidateEmailDomainBlockHappyPath() { - // no problem here - e := happyEmailDomainBlock() - err := gtsmodel.ValidateStruct(*e) - suite.NoError(err) -} - -func (suite *EmailDomainBlockValidateTestSuite) TestValidateEmailDomainBlockBadID() { - e := happyEmailDomainBlock() - - e.ID = "" - err := gtsmodel.ValidateStruct(*e) - suite.EqualError(err, "Key: 'EmailDomainBlock.ID' Error:Field validation for 'ID' failed on the 'required' tag") - - e.ID = "01FE96W293ZPRG9FQQP48HK8N001FE96W32AT24VYBGM12WN3GKB" - err = gtsmodel.ValidateStruct(*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 := gtsmodel.ValidateStruct(*e) - suite.NoError(err) -} - -func (suite *EmailDomainBlockValidateTestSuite) TestValidateEmailDomainBlockBadDomain() { - e := happyEmailDomainBlock() - - e.Domain = "" - err := gtsmodel.ValidateStruct(*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 = gtsmodel.ValidateStruct(*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 := gtsmodel.ValidateStruct(*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 = gtsmodel.ValidateStruct(*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)) -} diff --git a/internal/gtsmodel/emoji.go b/internal/gtsmodel/emoji.go index 71287130a..93c43c0f7 100644 --- a/internal/gtsmodel/emoji.go +++ b/internal/gtsmodel/emoji.go @@ -23,8 +23,8 @@ import "time" // Emoji represents a custom emoji that's been uploaded through the admin UI, and is useable by instance denizens. 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:",nullzero,notnull,default:current_timestamp"` // when was item created - UpdatedAt time.Time `validate:"-" bun:",nullzero,notnull,default:current_timestamp"` // when was item last updated + CreatedAt time.Time `validate:"-" bun:"type:timestamp,nullzero,notnull,default:current_timestamp"` // when was item created + UpdatedAt time.Time `validate:"-" bun:"type:timestamp,nullzero,notnull,default:current_timestamp"` // when was item last updated Shortcode string `validate:"required" bun:",notnull,unique:shortcodedomain"` // String shortcode for this emoji -- the part that's between colons. This should be lowercase a-z_ eg., 'blob_hug' 'purple_heart' Must be unique with domain. Domain string `validate:"omitempty,fqdn" bun:",notnull,default:'',unique:shortcodedomain"` // 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. @@ -37,7 +37,7 @@ type Emoji struct { 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:",nullzero,notnull,default:current_timestamp"` // When was the emoji image last updated? + ImageUpdatedAt time.Time `validate:"-" bun:"type:timestamp,nullzero,notnull,default:current_timestamp"` // When was the emoji image last updated? Disabled bool `validate:"-" bun:",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:",notnull,default:true"` // Is this emoji visible in the admin emoji picker? diff --git a/internal/gtsmodel/emoji_test.go b/internal/gtsmodel/emoji_test.go deleted file mode 100644 index a0b48040c..000000000 --- a/internal/gtsmodel/emoji_test.go +++ /dev/null @@ -1,194 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package gtsmodel_test - -import ( - "os" - "testing" - "time" - - "github.com/stretchr/testify/suite" - "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" -) - -func happyEmoji() *gtsmodel.Emoji { - // the file validator actually runs os.Stat on given paths, so we need to just create small - // temp files for both the main attachment file and the thumbnail - - imageFile, err := os.CreateTemp("", "gts_test_emoji") - if err != nil { - panic(err) - } - if _, err := imageFile.WriteString("main"); err != nil { - panic(err) - } - imagePath := imageFile.Name() - if err := imageFile.Close(); err != nil { - panic(err) - } - - staticFile, err := os.CreateTemp("", "gts_test_emoji_static") - if err != nil { - panic(err) - } - if _, err := staticFile.WriteString("thumbnail"); err != nil { - panic(err) - } - imageStaticPath := staticFile.Name() - if err := staticFile.Close(); err != nil { - panic(err) - } - - return >smodel.Emoji{ - ID: "01F8MH6NEM8D7527KZAECTCR76", - CreatedAt: time.Now().Add(-71 * time.Hour), - UpdatedAt: time.Now().Add(-71 * time.Hour), - Shortcode: "blob_test", - Domain: "example.org", - ImageRemoteURL: "https://example.org/emojis/blob_test.gif", - ImageStaticRemoteURL: "https://example.org/emojis/blob_test.png", - ImageURL: "", - ImageStaticURL: "", - ImagePath: imagePath, - ImageStaticPath: imageStaticPath, - ImageContentType: "image/gif", - ImageStaticContentType: "image/png", - ImageFileSize: 1024, - ImageStaticFileSize: 256, - ImageUpdatedAt: time.Now(), - Disabled: false, - URI: "https://example.org/emojis/blob_test", - VisibleInPicker: true, - CategoryID: "01FEE47ZH70PWDSEAVBRFNX325", - } -} - -type EmojiValidateTestSuite struct { - suite.Suite -} - -func (suite *EmojiValidateTestSuite) TestValidateEmojiHappyPath() { - // no problem here - m := happyEmoji() - err := gtsmodel.ValidateStruct(*m) - suite.NoError(err) -} - -func (suite *EmojiValidateTestSuite) TestValidateEmojiBadFilePaths() { - e := happyEmoji() - - e.ImagePath = "/tmp/nonexistent/file/for/gotosocial/test" - err := gtsmodel.ValidateStruct(*e) - suite.EqualError(err, "Key: 'Emoji.ImagePath' Error:Field validation for 'ImagePath' failed on the 'file' tag") - - e.ImagePath = "" - err = gtsmodel.ValidateStruct(*e) - suite.EqualError(err, "Key: 'Emoji.ImagePath' Error:Field validation for 'ImagePath' failed on the 'required' tag") - - e.ImagePath = "???????????thisnot a valid path####" - err = gtsmodel.ValidateStruct(*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 = gtsmodel.ValidateStruct(*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 = gtsmodel.ValidateStruct(*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 = gtsmodel.ValidateStruct(*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 := gtsmodel.ValidateStruct(*e) - suite.EqualError(err, "Key: 'Emoji.URI' Error:Field validation for 'URI' failed on the 'url' tag") - - e.URI = "" - err = gtsmodel.ValidateStruct(*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 := gtsmodel.ValidateStruct(*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 = gtsmodel.ValidateStruct(*e) - suite.NoError(err) - - e.ImageStaticRemoteURL = "" - err = gtsmodel.ValidateStruct(*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 = gtsmodel.ValidateStruct(*e) - suite.NoError(err) - - e.ImageURL = "" - e.ImageStaticURL = "" - e.ImageRemoteURL = "" - e.ImageStaticRemoteURL = "" - err = gtsmodel.ValidateStruct(*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 := gtsmodel.ValidateStruct(*e) - suite.EqualError(err, "Key: 'Emoji.ImageFileSize' Error:Field validation for 'ImageFileSize' failed on the 'required' tag") - - e.ImageStaticFileSize = 0 - err = gtsmodel.ValidateStruct(*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 = gtsmodel.ValidateStruct(*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 = gtsmodel.ValidateStruct(*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 := gtsmodel.ValidateStruct(*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 = gtsmodel.ValidateStruct(*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)) -} diff --git a/internal/gtsmodel/follow.go b/internal/gtsmodel/follow.go index c2b2633b9..8c4617f48 100644 --- a/internal/gtsmodel/follow.go +++ b/internal/gtsmodel/follow.go @@ -22,14 +22,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:",nullzero,notnull,default:current_timestamp"` // when was item created - UpdatedAt time.Time `validate:"-" bun:",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"` // 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"` // Who is the target of this follow ? - TargetAccount *Account `validate:"-" bun:"rel:belongs-to"` // Account corresponding to targetAccountID - ShowReblogs bool `validate:"-" bun:",nullzero,default:true"` // Does this follow also want to see reblogs and not just posts? - Notify bool `validate:"-" bun:",nullzero,default:false"` // does the following account want to be notified when the followed account posts? + 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:timestamp,nullzero,notnull,default:current_timestamp"` // when was item created + UpdatedAt time.Time `validate:"-" bun:"type:timestamp,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"` // 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"` // Who is the target of this follow ? + TargetAccount *Account `validate:"-" bun:"rel:belongs-to"` // Account corresponding to targetAccountID + ShowReblogs bool `validate:"-" bun:",nullzero,default:true"` // Does this follow also want to see reblogs and not just posts? + Notify bool `validate:"-" bun:",nullzero,default:false"` // does the following account want to be notified when the followed account posts? } diff --git a/internal/gtsmodel/follow_test.go b/internal/gtsmodel/follow_test.go deleted file mode 100644 index 2af0f5e4f..000000000 --- a/internal/gtsmodel/follow_test.go +++ /dev/null @@ -1,87 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package gtsmodel_test - -import ( - "testing" - "time" - - "github.com/stretchr/testify/suite" - "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" -) - -func happyFollow() *gtsmodel.Follow { - return >smodel.Follow{ - ID: "01FE91RJR88PSEEE30EV35QR8N", - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - AccountID: "01FE96MAE58MXCE5C4SSMEMCEK", - Account: nil, - TargetAccountID: "01FE96MXRHWZHKC0WH5FT82H1A", - TargetAccount: nil, - URI: "https://example.org/users/user1/activity/follow/01FE91RJR88PSEEE30EV35QR8N", - } -} - -type FollowValidateTestSuite struct { - suite.Suite -} - -func (suite *FollowValidateTestSuite) TestValidateFollowHappyPath() { - // no problem here - f := happyFollow() - err := gtsmodel.ValidateStruct(*f) - suite.NoError(err) -} - -func (suite *FollowValidateTestSuite) TestValidateFollowBadID() { - f := happyFollow() - - f.ID = "" - err := gtsmodel.ValidateStruct(*f) - suite.EqualError(err, "Key: 'Follow.ID' Error:Field validation for 'ID' failed on the 'required' tag") - - f.ID = "01FE96W293ZPRG9FQQP48HK8N001FE96W32AT24VYBGM12WN3GKB" - err = gtsmodel.ValidateStruct(*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 := gtsmodel.ValidateStruct(*f) - suite.NoError(err) -} - -func (suite *FollowValidateTestSuite) TestValidateFollowNoURI() { - f := happyFollow() - - f.URI = "" - err := gtsmodel.ValidateStruct(*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 = gtsmodel.ValidateStruct(*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)) -} diff --git a/internal/gtsmodel/followrequest.go b/internal/gtsmodel/followrequest.go index ae22f6487..9ffdb5938 100644 --- a/internal/gtsmodel/followrequest.go +++ b/internal/gtsmodel/followrequest.go @@ -22,14 +22,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:",nullzero,notnull,default:current_timestamp"` // when was item created - UpdatedAt time.Time `validate:"-" bun:",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"` // 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"` // Who is the target of this follow request? - TargetAccount *Account `validate:"-" bun:"rel:belongs-to"` // Account corresponding to targetAccountID - ShowReblogs bool `validate:"-" bun:",nullzero,default:true"` // Does this follow also want to see reblogs and not just posts? - Notify bool `validate:"-" bun:",nullzero,default:false"` // does the following account want to be notified when the followed account posts? + 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:timestamp,nullzero,notnull,default:current_timestamp"` // when was item created + UpdatedAt time.Time `validate:"-" bun:"type:timestamp,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"` // 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"` // Who is the target of this follow request? + TargetAccount *Account `validate:"-" bun:"rel:belongs-to"` // Account corresponding to targetAccountID + ShowReblogs bool `validate:"-" bun:",nullzero,default:true"` // Does this follow also want to see reblogs and not just posts? + Notify bool `validate:"-" bun:",nullzero,default:false"` // does the following account want to be notified when the followed account posts? } diff --git a/internal/gtsmodel/followrequest_test.go b/internal/gtsmodel/followrequest_test.go deleted file mode 100644 index a3ae8ded8..000000000 --- a/internal/gtsmodel/followrequest_test.go +++ /dev/null @@ -1,87 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package gtsmodel_test - -import ( - "testing" - "time" - - "github.com/stretchr/testify/suite" - "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" -) - -func happyFollowRequest() *gtsmodel.FollowRequest { - return >smodel.FollowRequest{ - ID: "01FE91RJR88PSEEE30EV35QR8N", - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - AccountID: "01FE96MAE58MXCE5C4SSMEMCEK", - Account: nil, - TargetAccountID: "01FE96MXRHWZHKC0WH5FT82H1A", - TargetAccount: nil, - URI: "https://example.org/users/user1/activity/follow/01FE91RJR88PSEEE30EV35QR8N", - } -} - -type FollowRequestValidateTestSuite struct { - suite.Suite -} - -func (suite *FollowRequestValidateTestSuite) TestValidateFollowRequestHappyPath() { - // no problem here - f := happyFollowRequest() - err := gtsmodel.ValidateStruct(*f) - suite.NoError(err) -} - -func (suite *FollowRequestValidateTestSuite) TestValidateFollowRequestBadID() { - f := happyFollowRequest() - - f.ID = "" - err := gtsmodel.ValidateStruct(*f) - suite.EqualError(err, "Key: 'FollowRequest.ID' Error:Field validation for 'ID' failed on the 'required' tag") - - f.ID = "01FE96W293ZPRG9FQQP48HK8N001FE96W32AT24VYBGM12WN3GKB" - err = gtsmodel.ValidateStruct(*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 := gtsmodel.ValidateStruct(*f) - suite.NoError(err) -} - -func (suite *FollowRequestValidateTestSuite) TestValidateFollowRequestNoURI() { - f := happyFollowRequest() - - f.URI = "" - err := gtsmodel.ValidateStruct(*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 = gtsmodel.ValidateStruct(*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)) -} diff --git a/internal/gtsmodel/instance.go b/internal/gtsmodel/instance.go index 4d36dbba8..a7cc8a034 100644 --- a/internal/gtsmodel/instance.go +++ b/internal/gtsmodel/instance.go @@ -1,3 +1,21 @@ +/* + GoToSocial + Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + package gtsmodel import "time" @@ -5,12 +23,12 @@ 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:",nullzero,notnull,default:current_timestamp"` // when was item created - UpdatedAt time.Time `validate:"-" bun:",nullzero,notnull,default:current_timestamp"` // when was item last updated + CreatedAt time.Time `validate:"-" bun:"type:timestamp,nullzero,notnull,default:current_timestamp"` // when was item created + UpdatedAt time.Time `validate:"-" bun:"type:timestamp,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:",nullzero"` // 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:",nullzero"` // When was this instance suspended, if at all? + SuspendedAt time.Time `validate:"-" bun:"type:timestamp,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:",nullzero"` // Short description of this instance diff --git a/internal/gtsmodel/instance_test.go b/internal/gtsmodel/instance_test.go deleted file mode 100644 index 5c685bb25..000000000 --- a/internal/gtsmodel/instance_test.go +++ /dev/null @@ -1,145 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package gtsmodel_test - -import ( - "testing" - "time" - - "github.com/stretchr/testify/suite" - "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" -) - -func happyInstance() *gtsmodel.Instance { - return >smodel.Instance{ - ID: "01FE91RJR88PSEEE30EV35QR8N", - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - Domain: "example.org", - Title: "Example Instance", - URI: "https://example.org", - SuspendedAt: time.Time{}, - DomainBlockID: "", - DomainBlock: nil, - ShortDescription: "This is a description for the example/testing instance.", - Description: "This is a way longer description for the example/testing instance!", - Terms: "Don't be a knobhead.", - ContactEmail: "admin@example.org", - ContactAccountUsername: "admin", - ContactAccountID: "01FEE20H5QWHJDEXAEE9G96PR0", - ContactAccount: nil, - Reputation: 420, - Version: "gotosocial 0.1.0", - } -} - -type InstanceValidateTestSuite struct { - suite.Suite -} - -func (suite *InstanceValidateTestSuite) TestValidateInstanceHappyPath() { - // no problem here - m := happyInstance() - err := gtsmodel.ValidateStruct(*m) - suite.NoError(err) -} - -func (suite *InstanceValidateTestSuite) TestValidateInstanceBadID() { - m := happyInstance() - - m.ID = "" - err := gtsmodel.ValidateStruct(*m) - suite.EqualError(err, "Key: 'Instance.ID' Error:Field validation for 'ID' failed on the 'required' tag") - - m.ID = "01FE96W293ZPRG9FQQP48HK8N001FE96W32AT24VYBGM12WN3GKB" - err = gtsmodel.ValidateStruct(*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 := gtsmodel.ValidateStruct(*i) - suite.EqualError(err, "Key: 'Instance.URI' Error:Field validation for 'URI' failed on the 'required' tag") - - i.URI = "---------------------------" - err = gtsmodel.ValidateStruct(*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 := gtsmodel.ValidateStruct(*i) - suite.EqualError(err, "Key: 'Instance.ContactAccountID' Error:Field validation for 'ContactAccountID' failed on the 'ulid' tag") - - i.ContactAccountID = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!!!!!!!!!!!!" - err = gtsmodel.ValidateStruct(*i) - suite.EqualError(err, "Key: 'Instance.ContactAccountID' Error:Field validation for 'ContactAccountID' failed on the 'ulid' tag") - - i.ContactAccountID = "" - err = gtsmodel.ValidateStruct(*i) - suite.EqualError(err, "Key: 'Instance.ContactAccountID' Error:Field validation for 'ContactAccountID' failed on the 'required_with' tag") - - i.ContactAccountUsername = "" - err = gtsmodel.ValidateStruct(*i) - suite.NoError(err) -} - -func (suite *InstanceValidateTestSuite) TestValidateInstanceDomain() { - i := happyInstance() - - i.Domain = "poopoo" - err := gtsmodel.ValidateStruct(*i) - suite.EqualError(err, "Key: 'Instance.Domain' Error:Field validation for 'Domain' failed on the 'fqdn' tag") - - i.Domain = "" - err = gtsmodel.ValidateStruct(*i) - suite.EqualError(err, "Key: 'Instance.Domain' Error:Field validation for 'Domain' failed on the 'required' tag") - - i.Domain = "https://aaaaaaaaaaaaah.org" - err = gtsmodel.ValidateStruct(*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 := gtsmodel.ValidateStruct(*i) - suite.EqualError(err, "Key: 'Instance.ContactEmail' Error:Field validation for 'ContactEmail' failed on the 'email' tag") - - i.ContactEmail = "" - err = gtsmodel.ValidateStruct(*i) - suite.NoError(err) -} - -func (suite *InstanceValidateTestSuite) TestValidateInstanceNoCreatedAt() { - i := happyInstance() - - i.CreatedAt = time.Time{} - err := gtsmodel.ValidateStruct(*i) - suite.NoError(err) -} - -func TestInstanceValidateTestSuite(t *testing.T) { - suite.Run(t, new(InstanceValidateTestSuite)) -} diff --git a/internal/gtsmodel/mediaattachment.go b/internal/gtsmodel/mediaattachment.go index 53f226ad7..59cf8aac1 100644 --- a/internal/gtsmodel/mediaattachment.go +++ b/internal/gtsmodel/mediaattachment.go @@ -26,8 +26,8 @@ import ( // 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:",nullzero,notnull,default:current_timestamp"` // when was item created - UpdatedAt time.Time `validate:"-" bun:",nullzero,notnull,default:current_timestamp"` // when was item last updated + CreatedAt time.Time `validate:"-" bun:"type:timestamp,nullzero,notnull,default:current_timestamp"` // when was item created + UpdatedAt time.Time `validate:"-" bun:"type:timestamp,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) @@ -47,20 +47,20 @@ type MediaAttachment struct { // 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:",nullzero,notnull"` // File size in bytes - UpdatedAt time.Time `validate:"-" bun:",nullzero,notnull,default:current_timestamp"` // When was the file last updated. + 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:",nullzero,notnull"` // File size in bytes + UpdatedAt time.Time `validate:"-" bun:"type:timestamp,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:",nullzero,notnull"` // File size in bytes - UpdatedAt time.Time `validate:"-" bun:",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 `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:",nullzero,notnull"` // File size in bytes + UpdatedAt time.Time `validate:"-" bun:"type:timestamp,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) } // ProcessingStatus refers to how far along in the processing stage the attachment is. diff --git a/internal/gtsmodel/mediaattachment_test.go b/internal/gtsmodel/mediaattachment_test.go deleted file mode 100644 index e1502ba62..000000000 --- a/internal/gtsmodel/mediaattachment_test.go +++ /dev/null @@ -1,229 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package gtsmodel_test - -import ( - "os" - "testing" - "time" - - "github.com/stretchr/testify/suite" - "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" -) - -func happyMediaAttachment() *gtsmodel.MediaAttachment { - // the file validator actually runs os.Stat on given paths, so we need to just create small - // temp files for both the main attachment file and the thumbnail - - mainFile, err := os.CreateTemp("", "gts_test_mainfile") - if err != nil { - panic(err) - } - if _, err := mainFile.WriteString("main"); err != nil { - panic(err) - } - mainPath := mainFile.Name() - if err := mainFile.Close(); err != nil { - panic(err) - } - - thumbnailFile, err := os.CreateTemp("", "gts_test_thumbnail") - if err != nil { - panic(err) - } - if _, err := thumbnailFile.WriteString("thumbnail"); err != nil { - panic(err) - } - thumbnailPath := thumbnailFile.Name() - if err := thumbnailFile.Close(); err != nil { - panic(err) - } - - return >smodel.MediaAttachment{ - ID: "01F8MH6NEM8D7527KZAECTCR76", - CreatedAt: time.Now().Add(-71 * time.Hour), - UpdatedAt: time.Now().Add(-71 * time.Hour), - StatusID: "01F8MH75CBF9JFX4ZAD54N0W0R", - URL: "http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/attachment/original/01F8MH6NEM8D7527KZAECTCR76.jpeg", - 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.jpeg", - RemoteURL: "", - }, - Avatar: false, - Header: false, - } -} - -type MediaAttachmentValidateTestSuite struct { - suite.Suite -} - -func (suite *MediaAttachmentValidateTestSuite) TestValidateMediaAttachmentHappyPath() { - // no problem here - m := happyMediaAttachment() - err := gtsmodel.ValidateStruct(*m) - suite.NoError(err) -} - -func (suite *MediaAttachmentValidateTestSuite) TestValidateMediaAttachmentBadFilePaths() { - m := happyMediaAttachment() - - m.File.Path = "/tmp/nonexistent/file/for/gotosocial/test" - err := gtsmodel.ValidateStruct(*m) - suite.EqualError(err, "Key: 'MediaAttachment.File.Path' Error:Field validation for 'Path' failed on the 'file' tag") - - m.File.Path = "" - err = gtsmodel.ValidateStruct(*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 = gtsmodel.ValidateStruct(*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 = gtsmodel.ValidateStruct(*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 = gtsmodel.ValidateStruct(*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 = gtsmodel.ValidateStruct(*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 := gtsmodel.ValidateStruct(*m) - suite.EqualError(err, "Key: 'MediaAttachment.Type' Error:Field validation for 'Type' failed on the 'oneof' tag") - - m.Type = "Not Supported" - err = gtsmodel.ValidateStruct(*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 := gtsmodel.ValidateStruct(*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 = gtsmodel.ValidateStruct(*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 = gtsmodel.ValidateStruct(*m) - suite.NoError(err) - - m.FileMeta.Focus.X = 3.6 - err = gtsmodel.ValidateStruct(*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 = gtsmodel.ValidateStruct(*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 := gtsmodel.ValidateStruct(*m) - suite.EqualError(err, "Key: 'MediaAttachment.URL' Error:Field validation for 'URL' failed on the 'url' tag") - - m.URL = "" - err = gtsmodel.ValidateStruct(*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 = gtsmodel.ValidateStruct(*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 = gtsmodel.ValidateStruct(*m) - suite.NoError(err) -} - -func (suite *MediaAttachmentValidateTestSuite) TestValidateMediaAttachmentBlurhash() { - m := happyMediaAttachment() - - m.Blurhash = "" - err := gtsmodel.ValidateStruct(*m) - suite.EqualError(err, "Key: 'MediaAttachment.Blurhash' Error:Field validation for 'Blurhash' failed on the 'required_if' tag") - - m.Type = gtsmodel.FileTypeAudio - err = gtsmodel.ValidateStruct(*m) - suite.NoError(err) - - m.Blurhash = "some_blurhash" - err = gtsmodel.ValidateStruct(*m) - suite.NoError(err) -} - -func (suite *MediaAttachmentValidateTestSuite) TestValidateMediaAttachmentProcessing() { - m := happyMediaAttachment() - - m.Processing = 420 - err := gtsmodel.ValidateStruct(*m) - suite.EqualError(err, "Key: 'MediaAttachment.Processing' Error:Field validation for 'Processing' failed on the 'oneof' tag") - - m.Processing = -5 - err = gtsmodel.ValidateStruct(*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)) -} diff --git a/internal/gtsmodel/mention.go b/internal/gtsmodel/mention.go index d8359745d..492740d77 100644 --- a/internal/gtsmodel/mention.go +++ b/internal/gtsmodel/mention.go @@ -22,17 +22,17 @@ import "time" // 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:",nullzero,notnull,default:current_timestamp"` // when was item created - UpdatedAt time.Time `validate:"-" bun:",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:",notnull,default:false"` // Prevent this mention from generating a notification? + 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:timestamp,nullzero,notnull,default:current_timestamp"` // when was item created + UpdatedAt time.Time `validate:"-" bun:"type:timestamp,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:",notnull,default:false"` // Prevent this mention from generating a notification? /* NON-DATABASE CONVENIENCE FIELDS diff --git a/internal/gtsmodel/mention_test.go b/internal/gtsmodel/mention_test.go deleted file mode 100644 index ac44e916b..000000000 --- a/internal/gtsmodel/mention_test.go +++ /dev/null @@ -1,101 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package gtsmodel_test - -import ( - "testing" - "time" - - "github.com/stretchr/testify/suite" - "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" -) - -func happyMention() *gtsmodel.Mention { - return >smodel.Mention{ - ID: "01FE91RJR88PSEEE30EV35QR8N", - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - OriginAccountID: "01FE96MAE58MXCE5C4SSMEMCEK", - OriginAccountURI: "https://some-instance/accounts/bleepbloop", - OriginAccount: nil, - TargetAccountID: "01FE96MXRHWZHKC0WH5FT82H1A", - TargetAccount: nil, - StatusID: "01FE96NBPNJNY26730FT6GZTFE", - Status: nil, - } -} - -type MentionValidateTestSuite struct { - suite.Suite -} - -func (suite *MentionValidateTestSuite) TestValidateMentionHappyPath() { - // no problem here - m := happyMention() - err := gtsmodel.ValidateStruct(*m) - suite.NoError(err) -} - -func (suite *MentionValidateTestSuite) TestValidateMentionBadID() { - m := happyMention() - - m.ID = "" - err := gtsmodel.ValidateStruct(*m) - suite.EqualError(err, "Key: 'Mention.ID' Error:Field validation for 'ID' failed on the 'required' tag") - - m.ID = "01FE96W293ZPRG9FQQP48HK8N001FE96W32AT24VYBGM12WN3GKB" - err = gtsmodel.ValidateStruct(*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 := gtsmodel.ValidateStruct(*m) - suite.EqualError(err, "Key: 'Mention.OriginAccountURI' Error:Field validation for 'OriginAccountURI' failed on the 'url' tag") - - m.OriginAccountURI = "---------------------------" - err = gtsmodel.ValidateStruct(*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 := gtsmodel.ValidateStruct(*m) - suite.EqualError(err, "Key: 'Mention.StatusID' Error:Field validation for 'StatusID' failed on the 'ulid' tag") - - m.StatusID = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!!!!!!!!!!!!" - err = gtsmodel.ValidateStruct(*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 := gtsmodel.ValidateStruct(*m) - suite.NoError(err) -} - -func TestMentionValidateTestSuite(t *testing.T) { - suite.Run(t, new(MentionValidateTestSuite)) -} diff --git a/internal/gtsmodel/notification.go b/internal/gtsmodel/notification.go index bb69bc8d4..1b1f39a77 100644 --- a/internal/gtsmodel/notification.go +++ b/internal/gtsmodel/notification.go @@ -22,8 +22,9 @@ 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:"ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database - CreatedAt time.Time `validate:"-" bun:",nullzero,notnull,default:current_timestamp"` // when was item created + 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:timestamp,nullzero,notnull,default:current_timestamp"` // when was item created + UpdatedAt time.Time `validate:"-" bun:"type:timestamp,nullzero,notnull,default:current_timestamp"` // when was item last updated // when was item created 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"` // Which account does this notification target (ie., who will receive the notification?) TargetAccount *Account `validate:"-" bun:"rel:belongs-to"` // Which account performed the action that created this notification? diff --git a/internal/gtsmodel/notification_test.go b/internal/gtsmodel/notification_test.go deleted file mode 100644 index 507a2cbfd..000000000 --- a/internal/gtsmodel/notification_test.go +++ /dev/null @@ -1,97 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package gtsmodel_test - -import ( - "testing" - "time" - - "github.com/stretchr/testify/suite" - "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" -) - -func happyNotification() *gtsmodel.Notification { - return >smodel.Notification{ - ID: "01FE91RJR88PSEEE30EV35QR8N", - CreatedAt: time.Now(), - NotificationType: gtsmodel.NotificationFave, - OriginAccountID: "01FE96MAE58MXCE5C4SSMEMCEK", - OriginAccount: nil, - TargetAccountID: "01FE96MXRHWZHKC0WH5FT82H1A", - TargetAccount: nil, - StatusID: "01FE96NBPNJNY26730FT6GZTFE", - Status: nil, - } -} - -type NotificationValidateTestSuite struct { - suite.Suite -} - -func (suite *NotificationValidateTestSuite) TestValidateNotificationHappyPath() { - // no problem here - m := happyNotification() - err := gtsmodel.ValidateStruct(*m) - suite.NoError(err) -} - -func (suite *NotificationValidateTestSuite) TestValidateNotificationBadID() { - m := happyNotification() - - m.ID = "" - err := gtsmodel.ValidateStruct(*m) - suite.EqualError(err, "Key: 'Notification.ID' Error:Field validation for 'ID' failed on the 'ulid' tag") - - m.ID = "01FE96W293ZPRG9FQQP48HK8N001FE96W32AT24VYBGM12WN3GKB" - err = gtsmodel.ValidateStruct(*m) - suite.EqualError(err, "Key: 'Notification.ID' Error:Field validation for 'ID' failed on the 'ulid' tag") -} - -func (suite *NotificationValidateTestSuite) TestValidateNotificationStatusID() { - m := happyNotification() - - m.StatusID = "" - err := gtsmodel.ValidateStruct(*m) - suite.EqualError(err, "Key: 'Notification.StatusID' Error:Field validation for 'StatusID' failed on the 'required_if' tag") - - m.StatusID = "9HZJ76B6VXSKF" - err = gtsmodel.ValidateStruct(*m) - suite.EqualError(err, "Key: 'Notification.StatusID' Error:Field validation for 'StatusID' failed on the 'ulid' tag") - - m.StatusID = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!!!!!!!!!!!!" - err = gtsmodel.ValidateStruct(*m) - suite.EqualError(err, "Key: 'Notification.StatusID' Error:Field validation for 'StatusID' failed on the 'ulid' tag") - - m.StatusID = "" - m.NotificationType = gtsmodel.NotificationFollowRequest - err = gtsmodel.ValidateStruct(*m) - suite.NoError(err) -} - -func (suite *NotificationValidateTestSuite) TestValidateNotificationNoCreatedAt() { - m := happyNotification() - - m.CreatedAt = time.Time{} - err := gtsmodel.ValidateStruct(*m) - suite.NoError(err) -} - -func TestNotificationValidateTestSuite(t *testing.T) { - suite.Run(t, new(NotificationValidateTestSuite)) -} diff --git a/internal/gtsmodel/poll.go b/internal/gtsmodel/poll.go deleted file mode 100644 index c39497cdd..000000000 --- a/internal/gtsmodel/poll.go +++ /dev/null @@ -1,19 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package gtsmodel diff --git a/internal/gtsmodel/relationship.go b/internal/gtsmodel/relationship.go deleted file mode 100644 index 3f753f6e9..000000000 --- a/internal/gtsmodel/relationship.go +++ /dev/null @@ -1,36 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package gtsmodel - -// Relationship describes a requester's relationship with another account. -type Relationship struct { - ID string // The account id. - Following bool // Are you following this user? - ShowingReblogs bool // Are you receiving this user's boosts in your home timeline? - Notifying bool // Have you enabled notifications for this user? - FollowedBy bool // Are you followed by this user? - Blocking bool // Are you blocking this user? - BlockedBy bool // Is this user blocking you? - Muting bool // Are you muting this user? - MutingNotifications bool // Are you muting notifications from this user? - Requested bool // Do you have a pending follow request for this user? - DomainBlocking bool // Are you blocking this user's domain? - Endorsed bool // Are you featuring this user on your profile? - Note string // Your note on this account. -} diff --git a/internal/gtsmodel/routersession.go b/internal/gtsmodel/routersession.go index 374264fe4..3edb8bc36 100644 --- a/internal/gtsmodel/routersession.go +++ b/internal/gtsmodel/routersession.go @@ -18,9 +18,13 @@ package gtsmodel +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"` - Auth []byte `validate:"required,len=32" bun:"type:bytea,notnull,nullzero"` - Crypt []byte `validate:"required,len=32" bun:"type:bytea,notnull,nullzero"` + 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:timestamp,nullzero,notnull,default:current_timestamp"` // when was item created + UpdatedAt time.Time `validate:"-" bun:"type:timestamp,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"` } diff --git a/internal/gtsmodel/routersession_test.go b/internal/gtsmodel/routersession_test.go deleted file mode 100644 index 3d6e1bcb0..000000000 --- a/internal/gtsmodel/routersession_test.go +++ /dev/null @@ -1,87 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package gtsmodel_test - -import ( - "testing" - - "github.com/stretchr/testify/suite" - "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" -) - -func happyRouterSession() *gtsmodel.RouterSession { - return >smodel.RouterSession{ - ID: "01FE91RJR88PSEEE30EV35QR8N", - Auth: []byte("12345678901234567890123456789012"), - Crypt: []byte("12345678901234567890123456789012"), - } -} - -type RouterSessionValidateTestSuite struct { - suite.Suite -} - -func (suite *RouterSessionValidateTestSuite) TestValidateRouterSessionHappyPath() { - // no problem here - r := happyRouterSession() - err := gtsmodel.ValidateStruct(*r) - suite.NoError(err) -} - -func (suite *RouterSessionValidateTestSuite) TestValidateRouterSessionAuth() { - r := happyRouterSession() - - // remove auth struct - r.Auth = nil - err := gtsmodel.ValidateStruct(*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 = gtsmodel.ValidateStruct(*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 = gtsmodel.ValidateStruct(*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 := gtsmodel.ValidateStruct(*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 = gtsmodel.ValidateStruct(*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 = gtsmodel.ValidateStruct(*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)) -} diff --git a/internal/gtsmodel/status.go b/internal/gtsmodel/status.go index d81a45ec3..f298e71cd 100644 --- a/internal/gtsmodel/status.go +++ b/internal/gtsmodel/status.go @@ -25,18 +25,18 @@ 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:",nullzero,notnull,default:current_timestamp"` // when was item created - UpdatedAt time.Time `validate:"-" bun:",nullzero,notnull,default:current_timestamp"` // when was item last updated + CreatedAt time.Time `validate:"-" bun:"type:timestamp,nullzero,notnull,default:current_timestamp"` // when was item created + UpdatedAt time.Time `validate:"-" bun:"type:timestamp,nullzero,notnull,default:current_timestamp"` // when was item last updated 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:",nullzero"` // content of this status; likely html-formatted but not guaranteed - AttachmentIDs []string `validate:"dive,ulid" bun:"attachments,array,nullzero"` // Database IDs of any media attachments associated with this status + 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,nullzero"` // Database IDs of any tags used in this status + 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,nullzero"` // Database IDs of any mentions in this status + 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,nullzero"` // Database IDs of any emojis used in this status + 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:",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? diff --git a/internal/gtsmodel/status_test.go b/internal/gtsmodel/status_test.go deleted file mode 100644 index 7f3b2f38f..000000000 --- a/internal/gtsmodel/status_test.go +++ /dev/null @@ -1,162 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package gtsmodel_test - -import ( - "testing" - "time" - - "github.com/stretchr/testify/suite" - "github.com/superseriousbusiness/gotosocial/internal/ap" - "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" -) - -func happyStatus() *gtsmodel.Status { - return >smodel.Status{ - ID: "01FEBBH6NYDG87NK6A6EC543ED", - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - URI: "https://example.org/users/test_user/statuses/01FEBBH6NYDG87NK6A6EC543ED", - URL: "https://example.org/@test_user/01FEBBH6NYDG87NK6A6EC543ED", - Content: "

Test status! #hello

", - AttachmentIDs: []string{"01FEBBKZBY9H5FEP3PHVVAAGN1", "01FEBBM7S2R4WT6WWW22KN1PWE"}, - Attachments: nil, - TagIDs: []string{"01FEBBNBMBSN1FESMZ1TCXNWYP"}, - Tags: nil, - MentionIDs: nil, - Mentions: nil, - EmojiIDs: nil, - Emojis: nil, - Local: true, - 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: false, - Language: "en", - CreatedWithApplicationID: "01FEBBZHF4GFVRXSJVXD0JTZZ2", - CreatedWithApplication: nil, - VisibilityAdvanced: gtsmodel.VisibilityAdvanced{ - Federated: true, - Boostable: true, - Replyable: true, - Likeable: true, - }, - ActivityStreamsType: ap.ObjectNote, - Text: "Test status! #hello", - Pinned: false, - } -} - -type StatusValidateTestSuite struct { - suite.Suite -} - -func (suite *StatusValidateTestSuite) TestValidateStatusHappyPath() { - // no problem here - s := happyStatus() - err := gtsmodel.ValidateStruct(*s) - suite.NoError(err) -} - -func (suite *StatusValidateTestSuite) TestValidateStatusBadID() { - s := happyStatus() - - s.ID = "" - err := gtsmodel.ValidateStruct(*s) - suite.EqualError(err, "Key: 'Status.ID' Error:Field validation for 'ID' failed on the 'required' tag") - - s.ID = "01FE96W293ZPRG9FQQP48HK8N001FE96W32AT24VYBGM12WN3GKB" - err = gtsmodel.ValidateStruct(*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 := gtsmodel.ValidateStruct(*s) - suite.EqualError(err, "Key: 'Status.AttachmentIDs[0]' Error:Field validation for 'AttachmentIDs[0]' failed on the 'ulid' tag") - - s.AttachmentIDs[0] = "01FE96W293ZPRG9FQQP48HK8N001FE96W32AT24VYBGM12WN3GKB" - err = gtsmodel.ValidateStruct(*s) - suite.EqualError(err, "Key: 'Status.AttachmentIDs[0]' Error:Field validation for 'AttachmentIDs[0]' failed on the 'ulid' tag") - - s.AttachmentIDs[1] = "" - err = gtsmodel.ValidateStruct(*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 = gtsmodel.ValidateStruct(*s) - suite.NoError(err) - - s.AttachmentIDs = nil - err = gtsmodel.ValidateStruct(*s) - suite.NoError(err) -} - -func (suite *StatusValidateTestSuite) TestStatusApplicationID() { - s := happyStatus() - - s.CreatedWithApplicationID = "" - err := gtsmodel.ValidateStruct(*s) - suite.EqualError(err, "Key: 'Status.CreatedWithApplicationID' Error:Field validation for 'CreatedWithApplicationID' failed on the 'required_if' tag") - - s.Local = false - err = gtsmodel.ValidateStruct(*s) - suite.NoError(err) -} - -func (suite *StatusValidateTestSuite) TestValidateStatusReplyFields() { - s := happyStatus() - - s.InReplyToAccountID = "01FEBCTP6DN7961PN81C3DVM4N " - err := gtsmodel.ValidateStruct(*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 = gtsmodel.ValidateStruct(*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 = gtsmodel.ValidateStruct(*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 = gtsmodel.ValidateStruct(*s) - suite.EqualError(err, "Key: 'Status.InReplyToID' Error:Field validation for 'InReplyToID' failed on the 'ulid' tag") - - s.InReplyToID = "01FEBD07E72DEY6YB9K10ZA6ST" - err = gtsmodel.ValidateStruct(*s) - suite.NoError(err) -} - -func TestStatusValidateTestSuite(t *testing.T) { - suite.Run(t, new(StatusValidateTestSuite)) -} diff --git a/internal/gtsmodel/statusbookmark.go b/internal/gtsmodel/statusbookmark.go index 76a2a866d..3dcf4cb92 100644 --- a/internal/gtsmodel/statusbookmark.go +++ b/internal/gtsmodel/statusbookmark.go @@ -22,12 +22,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:",nullzero,notnull,default:current_timestamp"` // when was item created - 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 `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database + CreatedAt time.Time `validate:"-" bun:"type:timestamp,nullzero,notnull,default:current_timestamp"` // when was item created + UpdatedAt time.Time `validate:"-" bun:"type:timestamp,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 } diff --git a/internal/gtsmodel/statusbookmark_test.go b/internal/gtsmodel/statusbookmark_test.go deleted file mode 100644 index e7a67fc35..000000000 --- a/internal/gtsmodel/statusbookmark_test.go +++ /dev/null @@ -1,87 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package gtsmodel_test - -import ( - "testing" - "time" - - "github.com/stretchr/testify/suite" - "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" -) - -func happyStatusBookmark() *gtsmodel.StatusBookmark { - return >smodel.StatusBookmark{ - ID: "01FE91RJR88PSEEE30EV35QR8N", - CreatedAt: time.Now(), - AccountID: "01FE96MAE58MXCE5C4SSMEMCEK", - Account: nil, - TargetAccountID: "01FE96MXRHWZHKC0WH5FT82H1A", - TargetAccount: nil, - StatusID: "01FE96NBPNJNY26730FT6GZTFE", - Status: nil, - } -} - -type StatusBookmarkValidateTestSuite struct { - suite.Suite -} - -func (suite *StatusBookmarkValidateTestSuite) TestValidateStatusBookmarkHappyPath() { - // no problem here - m := happyStatusBookmark() - err := gtsmodel.ValidateStruct(*m) - suite.NoError(err) -} - -func (suite *StatusBookmarkValidateTestSuite) TestValidateStatusBookmarkBadID() { - m := happyStatusBookmark() - - m.ID = "" - err := gtsmodel.ValidateStruct(*m) - suite.EqualError(err, "Key: 'StatusBookmark.ID' Error:Field validation for 'ID' failed on the 'required' tag") - - m.ID = "01FE96W293ZPRG9FQQP48HK8N001FE96W32AT24VYBGM12WN3GKB" - err = gtsmodel.ValidateStruct(*m) - suite.EqualError(err, "Key: 'StatusBookmark.ID' Error:Field validation for 'ID' failed on the 'ulid' tag") -} - -func (suite *StatusBookmarkValidateTestSuite) TestValidateStatusBookmarkDodgyStatusID() { - m := happyStatusBookmark() - - m.StatusID = "9HZJ76B6VXSKF" - err := gtsmodel.ValidateStruct(*m) - suite.EqualError(err, "Key: 'StatusBookmark.StatusID' Error:Field validation for 'StatusID' failed on the 'ulid' tag") - - m.StatusID = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!!!!!!!!!!!!" - err = gtsmodel.ValidateStruct(*m) - suite.EqualError(err, "Key: 'StatusBookmark.StatusID' Error:Field validation for 'StatusID' failed on the 'ulid' tag") -} - -func (suite *StatusBookmarkValidateTestSuite) TestValidateStatusBookmarkNoCreatedAt() { - m := happyStatusBookmark() - - m.CreatedAt = time.Time{} - err := gtsmodel.ValidateStruct(*m) - suite.NoError(err) -} - -func TestStatusBookmarkValidateTestSuite(t *testing.T) { - suite.Run(t, new(StatusBookmarkValidateTestSuite)) -} diff --git a/internal/gtsmodel/statusfave.go b/internal/gtsmodel/statusfave.go index 6647e941a..93bcda0e6 100644 --- a/internal/gtsmodel/statusfave.go +++ b/internal/gtsmodel/statusfave.go @@ -22,13 +22,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:",nullzero,notnull,default:current_timestamp"` // when was item created - AccountID string `validate:"required,ulid" bun:"type:CHAR(26),nullzero,notnull"` // id of the account that created ('did') the fave - Account *Account `validate:"-" bun:"rel:belongs-to"` // 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:"rel:belongs-to"` // account owning the faved status - StatusID string `validate:"required,ulid" bun:"type:CHAR(26),nullzero,notnull"` // database id of the status that has been 'faved' - Status *Status `validate:"-" bun:"rel:belongs-to"` // the faved status - URI string `validate:"required,url" bun:",nullzero,notnull"` // ActivityPub URI of this fave + 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:timestamp,nullzero,notnull,default:current_timestamp"` // when was item created + UpdatedAt time.Time `validate:"-" bun:"type:timestamp,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 fave + Account *Account `validate:"-" bun:"rel:belongs-to"` // 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:"rel:belongs-to"` // account owning the faved status + StatusID string `validate:"required,ulid" bun:"type:CHAR(26),nullzero,notnull"` // database id of the status that has been 'faved' + Status *Status `validate:"-" bun:"rel:belongs-to"` // the faved status + URI string `validate:"required,url" bun:",nullzero,notnull"` // ActivityPub URI of this fave } diff --git a/internal/gtsmodel/statusfave_test.go b/internal/gtsmodel/statusfave_test.go deleted file mode 100644 index 37f555a7c..000000000 --- a/internal/gtsmodel/statusfave_test.go +++ /dev/null @@ -1,100 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package gtsmodel_test - -import ( - "testing" - "time" - - "github.com/stretchr/testify/suite" - "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" -) - -func happyStatusFave() *gtsmodel.StatusFave { - return >smodel.StatusFave{ - ID: "01FE91RJR88PSEEE30EV35QR8N", - CreatedAt: time.Now(), - AccountID: "01FE96MAE58MXCE5C4SSMEMCEK", - Account: nil, - TargetAccountID: "01FE96MXRHWZHKC0WH5FT82H1A", - TargetAccount: nil, - StatusID: "01FE96NBPNJNY26730FT6GZTFE", - Status: nil, - URI: "https://example.org/users/user1/activity/faves/01FE91RJR88PSEEE30EV35QR8N", - } -} - -type StatusFaveValidateTestSuite struct { - suite.Suite -} - -func (suite *StatusFaveValidateTestSuite) TestValidateStatusFaveHappyPath() { - // no problem here - f := happyStatusFave() - err := gtsmodel.ValidateStruct(*f) - suite.NoError(err) -} - -func (suite *StatusFaveValidateTestSuite) TestValidateStatusFaveBadID() { - f := happyStatusFave() - - f.ID = "" - err := gtsmodel.ValidateStruct(*f) - suite.EqualError(err, "Key: 'StatusFave.ID' Error:Field validation for 'ID' failed on the 'required' tag") - - f.ID = "01FE96W293ZPRG9FQQP48HK8N001FE96W32AT24VYBGM12WN3GKB" - err = gtsmodel.ValidateStruct(*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 := gtsmodel.ValidateStruct(*f) - suite.EqualError(err, "Key: 'StatusFave.StatusID' Error:Field validation for 'StatusID' failed on the 'ulid' tag") - - f.StatusID = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!!!!!!!!!!!!" - err = gtsmodel.ValidateStruct(*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 := gtsmodel.ValidateStruct(*f) - suite.NoError(err) -} - -func (suite *StatusFaveValidateTestSuite) TestValidateStatusFaveNoURI() { - f := happyStatusFave() - - f.URI = "" - err := gtsmodel.ValidateStruct(*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 = gtsmodel.ValidateStruct(*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)) -} diff --git a/internal/gtsmodel/statusmute.go b/internal/gtsmodel/statusmute.go index 70789e557..2c03b8085 100644 --- a/internal/gtsmodel/statusmute.go +++ b/internal/gtsmodel/statusmute.go @@ -22,12 +22,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:",nullzero,notnull,default:current_timestamp"` // when was item created - 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 `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database + CreatedAt time.Time `validate:"-" bun:"type:timestamp,nullzero,notnull,default:current_timestamp"` // when was item created + UpdatedAt time.Time `validate:"-" bun:"type:timestamp,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 } diff --git a/internal/gtsmodel/statusmute_test.go b/internal/gtsmodel/statusmute_test.go deleted file mode 100644 index b3926bb69..000000000 --- a/internal/gtsmodel/statusmute_test.go +++ /dev/null @@ -1,87 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package gtsmodel_test - -import ( - "testing" - "time" - - "github.com/stretchr/testify/suite" - "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" -) - -func happyStatusMute() *gtsmodel.StatusMute { - return >smodel.StatusMute{ - ID: "01FE91RJR88PSEEE30EV35QR8N", - CreatedAt: time.Now(), - AccountID: "01FE96MAE58MXCE5C4SSMEMCEK", - Account: nil, - TargetAccountID: "01FE96MXRHWZHKC0WH5FT82H1A", - TargetAccount: nil, - StatusID: "01FE96NBPNJNY26730FT6GZTFE", - Status: nil, - } -} - -type StatusMuteValidateTestSuite struct { - suite.Suite -} - -func (suite *StatusMuteValidateTestSuite) TestValidateStatusMuteHappyPath() { - // no problem here - m := happyStatusMute() - err := gtsmodel.ValidateStruct(*m) - suite.NoError(err) -} - -func (suite *StatusMuteValidateTestSuite) TestValidateStatusMuteBadID() { - m := happyStatusMute() - - m.ID = "" - err := gtsmodel.ValidateStruct(*m) - suite.EqualError(err, "Key: 'StatusMute.ID' Error:Field validation for 'ID' failed on the 'required' tag") - - m.ID = "01FE96W293ZPRG9FQQP48HK8N001FE96W32AT24VYBGM12WN3GKB" - err = gtsmodel.ValidateStruct(*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 := gtsmodel.ValidateStruct(*m) - suite.EqualError(err, "Key: 'StatusMute.StatusID' Error:Field validation for 'StatusID' failed on the 'ulid' tag") - - m.StatusID = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!!!!!!!!!!!!" - err = gtsmodel.ValidateStruct(*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 := gtsmodel.ValidateStruct(*m) - suite.NoError(err) -} - -func TestStatusMuteValidateTestSuite(t *testing.T) { - suite.Run(t, new(StatusMuteValidateTestSuite)) -} diff --git a/internal/gtsmodel/stream.go b/internal/gtsmodel/stream.go deleted file mode 100644 index 4a1571de5..000000000 --- a/internal/gtsmodel/stream.go +++ /dev/null @@ -1,38 +0,0 @@ -package gtsmodel - -import "sync" - -// StreamsForAccount is a wrapper for the multiple streams that one account can have running at the same time. -// TODO: put a limit on this -type StreamsForAccount struct { - // The currently held streams for this account - Streams []*Stream - // Mutex to lock/unlock when modifying the slice of streams. - sync.Mutex -} - -// Stream represents one open stream for a client. -type Stream struct { - // ID of this stream, generated during creation. - ID string - // Type of this stream: user/public/etc - Type string - // Channel of messages for the client to read from - Messages chan *Message - // Channel to close when the client drops away - Hangup chan interface{} - // Only put messages in the stream when Connected - Connected bool - // Mutex to lock/unlock when inserting messages, hanging up, changing the connected state etc. - sync.Mutex -} - -// Message represents one streamed message. -type Message struct { - // All the stream types this message should be delivered to. - Stream []string `json:"stream"` - // The event type of the message (update/delete/notification etc) - Event string `json:"event"` - // The actual payload of the message. In case of an update or notification, this will be a JSON string. - Payload string `json:"payload"` -} diff --git a/internal/gtsmodel/tag.go b/internal/gtsmodel/tag.go index 14ff26f87..295447c4f 100644 --- a/internal/gtsmodel/tag.go +++ b/internal/gtsmodel/tag.go @@ -22,13 +22,13 @@ 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:",nullzero,notnull,default:current_timestamp"` // when was item created - UpdatedAt time.Time `validate:"-" bun:",nullzero,notnull,default:current_timestamp"` // when was item last updated - URL string `validate:"required,url" bun:",nullzero,notnull"` // Href/web address of this tag, eg https://example.org/tags/somehashtag - Name string `validate:"required" bun:",unique,nullzero,notnull"` // name of this tag -- the tag without the hash part - FirstSeenFromAccountID string `validate:"omitempty,ulid" bun:"type:CHAR(26),nullzero"` // Which account ID is the first one we saw using this tag? - Useable bool `validate:"-" bun:",notnull,default:true"` // can our instance users use this tag? - Listable bool `validate:"-" bun:",notnull,default:true"` // can our instance users look up this tag? - LastStatusAt time.Time `validate:"-" bun:",nullzero,notnull,default:current_timestamp"` // when was this tag last used? + 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:timestamp,nullzero,notnull,default:current_timestamp"` // when was item created + UpdatedAt time.Time `validate:"-" bun:"type:timestamp,nullzero,notnull,default:current_timestamp"` // when was item last updated + URL string `validate:"required,url" bun:",nullzero,notnull"` // Href/web address of this tag, eg https://example.org/tags/somehashtag + Name string `validate:"required" bun:",unique,nullzero,notnull"` // name of this tag -- the tag without the hash part + FirstSeenFromAccountID string `validate:"omitempty,ulid" bun:"type:CHAR(26),nullzero"` // Which account ID is the first one we saw using this tag? + Useable bool `validate:"-" bun:",notnull,default:true"` // can our instance users use this tag? + Listable bool `validate:"-" bun:",notnull,default:true"` // can our instance users look up this tag? + LastStatusAt time.Time `validate:"-" bun:"type:timestamp,nullzero,notnull,default:current_timestamp"` // when was this tag last used? } diff --git a/internal/gtsmodel/tag_test.go b/internal/gtsmodel/tag_test.go deleted file mode 100644 index baafe55bd..000000000 --- a/internal/gtsmodel/tag_test.go +++ /dev/null @@ -1,92 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package gtsmodel_test - -import ( - "testing" - "time" - - "github.com/stretchr/testify/suite" - "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" -) - -func happyTag() *gtsmodel.Tag { - return >smodel.Tag{ - ID: "01FE91RJR88PSEEE30EV35QR8N", - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - URL: "https://example.org/tags/some_tag", - Name: "some_tag", - FirstSeenFromAccountID: "01FE91SR5P2GW06K3AJ98P72MT", - Useable: true, - Listable: true, - LastStatusAt: time.Now(), - } -} - -type TagValidateTestSuite struct { - suite.Suite -} - -func (suite *TagValidateTestSuite) TestValidateTagHappyPath() { - // no problem here - t := happyTag() - err := gtsmodel.ValidateStruct(*t) - suite.NoError(err) -} - -func (suite *TagValidateTestSuite) TestValidateTagNoName() { - t := happyTag() - t.Name = "" - - err := gtsmodel.ValidateStruct(*t) - suite.EqualError(err, "Key: 'Tag.Name' Error:Field validation for 'Name' failed on the 'required' tag") -} - -func (suite *TagValidateTestSuite) TestValidateTagBadURL() { - t := happyTag() - - t.URL = "" - err := gtsmodel.ValidateStruct(*t) - suite.EqualError(err, "Key: 'Tag.URL' Error:Field validation for 'URL' failed on the 'required' tag") - - t.URL = "no-schema.com" - err = gtsmodel.ValidateStruct(*t) - suite.EqualError(err, "Key: 'Tag.URL' Error:Field validation for 'URL' failed on the 'url' tag") - - t.URL = "justastring" - err = gtsmodel.ValidateStruct(*t) - suite.EqualError(err, "Key: 'Tag.URL' Error:Field validation for 'URL' failed on the 'url' tag") - - t.URL = "https://aaa\n\n\naaaaaaaa" - err = gtsmodel.ValidateStruct(*t) - suite.EqualError(err, "Key: 'Tag.URL' Error:Field validation for 'URL' failed on the 'url' tag") -} - -func (suite *TagValidateTestSuite) TestValidateTagNoFirstSeenFromAccountID() { - t := happyTag() - t.FirstSeenFromAccountID = "" - - err := gtsmodel.ValidateStruct(*t) - suite.NoError(err) -} - -func TestTagValidateTestSuite(t *testing.T) { - suite.Run(t, new(TagValidateTestSuite)) -} diff --git a/internal/gtsmodel/token.go b/internal/gtsmodel/token.go index 1ede26aee..65728ac60 100644 --- a/internal/gtsmodel/token.go +++ b/internal/gtsmodel/token.go @@ -21,30 +21,23 @@ package gtsmodel import "time" // Token is a translation of the gotosocial token with the ExpiresIn fields replaced with ExpiresAt. -// -// Explanation for this: gotosocial assumes an in-memory or file database of some kind, where a time-to-live parameter (TTL) can be defined, -// and tokens with expired TTLs are automatically removed. Since some databases don't have that feature, it's easier to set an expiry time and -// then periodically sweep out tokens when that time has passed. -// -// Note that this struct does *not* satisfy the token interface shown here: https://github.com/superseriousbusiness/oauth2/blob/master/model.go#L22 -// and implemented here: https://github.com/superseriousbusiness/oauth2/blob/master/models/token.go. -// As such, manual translation is always required between Token and the gotosocial *model.Token. The helper functions oauthTokenToPGToken -// and pgTokenToOauthToken can be used for that. type Token struct { - ID string `validate:"ulid" bun:"type:CHAR(26),pk,nullzero,notnull"` - ClientID string - UserID string - RedirectURI string - Scope string - Code string `bun:"default:'',pk"` - CodeChallenge string - CodeChallengeMethod string - CodeCreateAt time.Time `bun:",nullzero"` - CodeExpiresAt time.Time `bun:",nullzero"` - Access string `bun:"default:'',pk"` - AccessCreateAt time.Time `bun:",nullzero"` - AccessExpiresAt time.Time `bun:",nullzero"` - Refresh string `bun:"default:'',pk"` - RefreshCreateAt time.Time `bun:",nullzero"` - RefreshExpiresAt time.Time `bun:",nullzero"` + 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:timestamp,nullzero,notnull,default:current_timestamp"` // when was item created + UpdatedAt time.Time `validate:"-" bun:"type:timestamp,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,notnull"` // ID of the user who owns this token + RedirectURI string `validate:"required,url" bun:",nullzero,notnull"` // Oauth redirect URI for this token + Scope string `validate:"omitempty,url" bun:",nullzero,notnull,default:'read'"` // 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:timestamp,nullzero"` // Code created time, if code present + CodeExpiresAt time.Time `validate:"-" bun:"type:timestamp,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:timestamp,nullzero"` // User level access token created time, if access present + AccessExpiresAt time.Time `validate:"-" bun:"type:timestamp,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:timestamp,nullzero"` // Refresh created at, if refresh present + RefreshExpiresAt time.Time `validate:"-" bun:"type:timestamp,nullzero"` // Refresh expires at -- null means the refresh token never expires } diff --git a/internal/gtsmodel/user.go b/internal/gtsmodel/user.go index e0568d6a0..70f328619 100644 --- a/internal/gtsmodel/user.go +++ b/internal/gtsmodel/user.go @@ -26,45 +26,34 @@ 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:",nullzero,notnull,default:current_timestamp"` // when was item created - UpdatedAt time.Time `validate:"-" bun:",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:",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:",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:"-" bun:",nullzero,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:"required,ulid" bun:"type:CHAR(26),nullzero,notnull"` // 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:",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:",nullzero"` // When did we send email confirmation to this user? - ConfirmedAt time.Time `validate:"required_with=Email" bun:",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:",notnull,default:false"` // Is this user a moderator? - Admin bool `validate:"-" bun:",notnull,default:false"` // Is this user an admin? - Disabled bool `validate:"-" bun:",notnull,default:false"` // Is this user disabled from posting? - Approved bool `validate:"-" bun:",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:",nullzero"` // When did we email the user their reset-password email? - - EncryptedOTPSecret string `validate:"-" bun:",nullzero"` - EncryptedOTPSecretIv string `validate:"-" bun:",nullzero"` - EncryptedOTPSecretSalt string `validate:"-" bun:",nullzero"` - OTPRequiredForLogin bool `validate:"-" bun:",notnull,default:false"` - OTPBackupCodes []string `validate:"-" bun:",nullzero"` - ConsumedTimestamp int `validate:"-" bun:",nullzero"` - RememberToken string `validate:"-" bun:",nullzero"` - SignInToken string `validate:"-" bun:",nullzero"` - SignInTokenSentAt time.Time `validate:"-" bun:",nullzero"` - WebauthnID string `validate:"-" bun:",nullzero"` + 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:timestamp,nullzero,notnull,default:current_timestamp"` // when was item created + UpdatedAt time.Time `validate:"-" bun:"type:timestamp,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:timestamp,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:timestamp,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:"-" bun:",nullzero,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:timestamp,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:timestamp,nullzero"` // When did we send email confirmation to this user? + ConfirmedAt time.Time `validate:"required_with=Email" bun:"type:timestamp,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:",notnull,default:false"` // Is this user a moderator? + Admin bool `validate:"-" bun:",notnull,default:false"` // Is this user an admin? + Disabled bool `validate:"-" bun:",notnull,default:false"` // Is this user disabled from posting? + Approved bool `validate:"-" bun:",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:timestamp,nullzero"` // When did we email the user their reset-password email? } diff --git a/internal/gtsmodel/user_test.go b/internal/gtsmodel/user_test.go deleted file mode 100644 index a13b31075..000000000 --- a/internal/gtsmodel/user_test.go +++ /dev/null @@ -1,124 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package gtsmodel_test - -import ( - "net" - "testing" - "time" - - "github.com/stretchr/testify/suite" - "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" -) - -func happyUser() *gtsmodel.User { - return >smodel.User{ - ID: "01FE8TTK9F34BR0KG7639AJQTX", - Email: "whatever@example.org", - AccountID: "01FE8TWA7CN8J7237K5DFS1RY5", - Account: nil, - EncryptedPassword: "$2y$10$tkRapNGW.RWkEuCMWdgArunABFvsPGRvFQY3OibfSJo0RDL3z8WfC", - CreatedAt: time.Now(), - UpdatedAt: time.Now(), - SignUpIP: net.ParseIP("128.64.32.16"), - CurrentSignInAt: time.Now(), - CurrentSignInIP: net.ParseIP("128.64.32.16"), - LastSignInAt: time.Now(), - LastSignInIP: net.ParseIP("128.64.32.16"), - SignInCount: 0, - InviteID: "", - ChosenLanguages: []string{}, - FilteredLanguages: []string{}, - Locale: "en", - CreatedByApplicationID: "01FE8Y5EHMWCA1MHMTNHRVZ1X4", - CreatedByApplication: nil, - LastEmailedAt: time.Now(), - ConfirmationToken: "", - ConfirmedAt: time.Now(), - ConfirmationSentAt: time.Time{}, - UnconfirmedEmail: "", - Moderator: false, - Admin: false, - Disabled: false, - Approved: true, - } -} - -type UserValidateTestSuite struct { - suite.Suite -} - -func (suite *UserValidateTestSuite) TestValidateUserHappyPath() { - // no problem here - u := happyUser() - err := gtsmodel.ValidateStruct(*u) - suite.NoError(err) -} - -func (suite *UserValidateTestSuite) TestValidateUserNoID() { - // user has no id set - u := happyUser() - u.ID = "" - - err := gtsmodel.ValidateStruct(*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 := gtsmodel.ValidateStruct(*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 := gtsmodel.ValidateStruct(*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 := gtsmodel.ValidateStruct(*u) - suite.NoError(err) -} - -func (suite *UserValidateTestSuite) TestValidateUserNoConfirmedAt() { - // user has Email but no ConfirmedAt - u := happyUser() - u.ConfirmedAt = time.Time{} - - err := gtsmodel.ValidateStruct(*u) - suite.EqualError(err, "Key: 'User.ConfirmedAt' Error:Field validation for 'ConfirmedAt' failed on the 'required_with' tag") -} - -func TestUserValidateTestSuite(t *testing.T) { - suite.Run(t, new(UserValidateTestSuite)) -} diff --git a/internal/gtsmodel/validate.go b/internal/gtsmodel/validate.go deleted file mode 100644 index 0e1957b28..000000000 --- a/internal/gtsmodel/validate.go +++ /dev/null @@ -1,75 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package gtsmodel - -import ( - "reflect" - - "github.com/go-playground/validator/v10" - "github.com/superseriousbusiness/gotosocial/internal/util" -) - -var v *validator.Validate - -// Validation Panic messages -const ( - PointerValidationPanic = "validate function was passed pointer" - InvalidValidationPanic = "validate function was passed invalid item" -) - -func ulidValidator(fl validator.FieldLevel) bool { - field := fl.Field() - - switch field.Kind() { - case reflect.String: - return util.ValidateULID(field.String()) - default: - return false - } -} - -func init() { - v = validator.New() - v.RegisterValidation("ulid", ulidValidator) -} - -// ValidateStruct validates the passed struct, returning validator.ValidationErrors if invalid, or nil if OK. -func ValidateStruct(s interface{}) error { - switch reflect.ValueOf(s).Kind() { - case reflect.Invalid: - panic(InvalidValidationPanic) - case reflect.Ptr: - panic(PointerValidationPanic) - } - - err := v.Struct(s) - return processValidationError(err) -} - -func processValidationError(err error) error { - if err == nil { - return nil - } - - if ive, ok := err.(*validator.InvalidValidationError); ok { - panic(ive) - } - - return err.(validator.ValidationErrors) -} diff --git a/internal/gtsmodel/validate_test.go b/internal/gtsmodel/validate_test.go deleted file mode 100644 index 7200522bc..000000000 --- a/internal/gtsmodel/validate_test.go +++ /dev/null @@ -1,64 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package gtsmodel_test - -import ( - "testing" - - "github.com/stretchr/testify/suite" - "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" -) - -type ValidateTestSuite struct { - suite.Suite -} - -func (suite *ValidateTestSuite) TestValidatePointer() { - var nilUser *gtsmodel.User - suite.PanicsWithValue(gtsmodel.PointerValidationPanic, func() { - gtsmodel.ValidateStruct(nilUser) - }) -} - -func (suite *ValidateTestSuite) TestValidateNil() { - suite.PanicsWithValue(gtsmodel.InvalidValidationPanic, func() { - gtsmodel.ValidateStruct(nil) - }) -} - -func (suite *ValidateTestSuite) TestValidateWeirdULID() { - type a struct { - ID bool `validate:"required,ulid"` - } - - err := gtsmodel.ValidateStruct(a{ID: true}) - suite.Error(err) -} - -func (suite *ValidateTestSuite) TestValidateNotStruct() { - type aaaaaaa string - aaaaaa := aaaaaaa("aaaa") - suite.Panics(func() { - gtsmodel.ValidateStruct(aaaaaa) - }) -} - -func TestValidateTestSuite(t *testing.T) { - suite.Run(t, new(ValidateTestSuite)) -} diff --git a/internal/oauth/clientstore_test.go b/internal/oauth/clientstore_test.go index fd3452405..b0a36487b 100644 --- a/internal/oauth/clientstore_test.go +++ b/internal/oauth/clientstore_test.go @@ -42,9 +42,9 @@ const () // SetupSuite sets some variables on the suite that we can use as consts (more or less) throughout func (suite *PgClientStoreTestSuite) SetupSuite() { suite.testClientID = "01FCVB74EW6YBYAEY7QG9CQQF6" - suite.testClientSecret = "test-client-secret" + suite.testClientSecret = "4cc87402-259b-4a35-9485-2c8bf54f3763" suite.testClientDomain = "https://example.org" - suite.testClientUserID = "test-client-user-id" + suite.testClientUserID = "01FEGYXKVCDB731QF9MVFXA4F5" } // SetupTest creates a postgres connection and creates the oauth_clients table before each test diff --git a/internal/processing/account/update.go b/internal/processing/account/update.go index 5cc95b71f..c0fee8e25 100644 --- a/internal/processing/account/update.go +++ b/internal/processing/account/update.go @@ -32,7 +32,7 @@ import ( "github.com/superseriousbusiness/gotosocial/internal/media" "github.com/superseriousbusiness/gotosocial/internal/messages" "github.com/superseriousbusiness/gotosocial/internal/text" - "github.com/superseriousbusiness/gotosocial/internal/util" + "github.com/superseriousbusiness/gotosocial/internal/validate" ) func (p *processor) Update(ctx context.Context, account *gtsmodel.Account, form *apimodel.UpdateCredentialsRequest) (*apimodel.Account, error) { @@ -51,7 +51,7 @@ func (p *processor) Update(ctx context.Context, account *gtsmodel.Account, form } if form.DisplayName != nil { - if err := util.ValidateDisplayName(*form.DisplayName); err != nil { + if err := validate.DisplayName(*form.DisplayName); err != nil { return nil, err } displayName := text.RemoveHTML(*form.DisplayName) // no html allowed in display name @@ -61,7 +61,7 @@ func (p *processor) Update(ctx context.Context, account *gtsmodel.Account, form } if form.Note != nil { - if err := util.ValidateNote(*form.Note); err != nil { + if err := validate.Note(*form.Note); err != nil { return nil, err } note := text.SanitizeHTML(*form.Note) // html OK in note but sanitize it @@ -94,7 +94,7 @@ func (p *processor) Update(ctx context.Context, account *gtsmodel.Account, form if form.Source != nil { if form.Source.Language != nil { - if err := util.ValidateLanguage(*form.Source.Language); err != nil { + if err := validate.Language(*form.Source.Language); err != nil { return nil, err } if err := p.db.UpdateOneByID(ctx, account.ID, "language", *form.Source.Language, >smodel.Account{}); err != nil { @@ -109,7 +109,7 @@ func (p *processor) Update(ctx context.Context, account *gtsmodel.Account, form } if form.Source.Privacy != nil { - if err := util.ValidatePrivacy(*form.Source.Privacy); err != nil { + if err := validate.Privacy(*form.Source.Privacy); err != nil { return nil, err } if err := p.db.UpdateOneByID(ctx, account.ID, "privacy", *form.Source.Privacy, >smodel.Account{}); err != nil { diff --git a/internal/processing/app.go b/internal/processing/app.go index fc6814196..d6ded6efa 100644 --- a/internal/processing/app.go +++ b/internal/processing/app.go @@ -43,7 +43,6 @@ func (p *processor) AppCreate(ctx context.Context, authed *oauth.Auth, form *api return nil, err } clientSecret := uuid.NewString() - vapidKey := uuid.NewString() appID, err := id.NewRandomULID() if err != nil { @@ -59,7 +58,6 @@ func (p *processor) AppCreate(ctx context.Context, authed *oauth.Auth, form *api ClientID: clientID, ClientSecret: clientSecret, Scopes: scopes, - VapidKey: vapidKey, } // chuck it in the db diff --git a/internal/processing/instance.go b/internal/processing/instance.go index ced798c2e..e74d3077a 100644 --- a/internal/processing/instance.go +++ b/internal/processing/instance.go @@ -27,7 +27,7 @@ import ( "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/text" - "github.com/superseriousbusiness/gotosocial/internal/util" + "github.com/superseriousbusiness/gotosocial/internal/validate" ) func (p *processor) InstanceGet(ctx context.Context, domain string) (*apimodel.Instance, gtserror.WithCode) { @@ -59,7 +59,7 @@ func (p *processor) InstancePatch(ctx context.Context, form *apimodel.InstanceSe // validate & update site title if it's set on the form if form.Title != nil { - if err := util.ValidateSiteTitle(*form.Title); err != nil { + if err := validate.SiteTitle(*form.Title); err != nil { return nil, gtserror.NewErrorBadRequest(err, fmt.Sprintf("site title invalid: %s", err)) } i.Title = text.RemoveHTML(*form.Title) // don't allow html in site title @@ -101,7 +101,7 @@ func (p *processor) InstancePatch(ctx context.Context, form *apimodel.InstanceSe // validate & update site contact email if it's set on the form if form.ContactEmail != nil { - if err := util.ValidateEmail(*form.ContactEmail); err != nil { + if err := validate.Email(*form.ContactEmail); err != nil { return nil, gtserror.NewErrorBadRequest(err, err.Error()) } i.ContactEmail = *form.ContactEmail @@ -109,7 +109,7 @@ func (p *processor) InstancePatch(ctx context.Context, form *apimodel.InstanceSe // validate & update site short description if it's set on the form if form.ShortDescription != nil { - if err := util.ValidateSiteShortDescription(*form.ShortDescription); err != nil { + if err := validate.SiteShortDescription(*form.ShortDescription); err != nil { return nil, gtserror.NewErrorBadRequest(err, err.Error()) } i.ShortDescription = text.SanitizeHTML(*form.ShortDescription) // html is OK in site description, but we should sanitize it @@ -117,7 +117,7 @@ func (p *processor) InstancePatch(ctx context.Context, form *apimodel.InstanceSe // validate & update site description if it's set on the form if form.Description != nil { - if err := util.ValidateSiteDescription(*form.Description); err != nil { + if err := validate.SiteDescription(*form.Description); err != nil { return nil, gtserror.NewErrorBadRequest(err, err.Error()) } i.Description = text.SanitizeHTML(*form.Description) // html is OK in site description, but we should sanitize it @@ -125,7 +125,7 @@ func (p *processor) InstancePatch(ctx context.Context, form *apimodel.InstanceSe // validate & update site terms if it's set on the form if form.Terms != nil { - if err := util.ValidateSiteTerms(*form.Terms); err != nil { + if err := validate.SiteTerms(*form.Terms); err != nil { return nil, gtserror.NewErrorBadRequest(err, err.Error()) } i.Terms = text.SanitizeHTML(*form.Terms) // html is OK in site terms, but we should sanitize it diff --git a/internal/processing/processor.go b/internal/processing/processor.go index 1ade38564..38076123f 100644 --- a/internal/processing/processor.go +++ b/internal/processing/processor.go @@ -39,6 +39,7 @@ import ( mediaProcessor "github.com/superseriousbusiness/gotosocial/internal/processing/media" "github.com/superseriousbusiness/gotosocial/internal/processing/status" "github.com/superseriousbusiness/gotosocial/internal/processing/streaming" + "github.com/superseriousbusiness/gotosocial/internal/stream" "github.com/superseriousbusiness/gotosocial/internal/timeline" "github.com/superseriousbusiness/gotosocial/internal/typeutils" "github.com/superseriousbusiness/gotosocial/internal/visibility" @@ -166,7 +167,7 @@ type Processor interface { // AuthorizeStreamingRequest returns a gotosocial account in exchange for an access token, or an error if the given token is not valid. AuthorizeStreamingRequest(ctx context.Context, accessToken string) (*gtsmodel.Account, error) // OpenStreamForAccount opens a new stream for the given account, with the given stream type. - OpenStreamForAccount(ctx context.Context, account *gtsmodel.Account, streamType string) (*gtsmodel.Stream, gtserror.WithCode) + OpenStreamForAccount(ctx context.Context, account *gtsmodel.Account, streamType string) (*stream.Stream, gtserror.WithCode) /* FEDERATION API-FACING PROCESSING FUNCTIONS diff --git a/internal/processing/streaming.go b/internal/processing/streaming.go index e1c134d00..fd5113b0d 100644 --- a/internal/processing/streaming.go +++ b/internal/processing/streaming.go @@ -23,12 +23,13 @@ import ( "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" + "github.com/superseriousbusiness/gotosocial/internal/stream" ) func (p *processor) AuthorizeStreamingRequest(ctx context.Context, accessToken string) (*gtsmodel.Account, error) { return p.streamingProcessor.AuthorizeStreamingRequest(ctx, accessToken) } -func (p *processor) OpenStreamForAccount(ctx context.Context, account *gtsmodel.Account, streamType string) (*gtsmodel.Stream, gtserror.WithCode) { +func (p *processor) OpenStreamForAccount(ctx context.Context, account *gtsmodel.Account, streamType string) (*stream.Stream, gtserror.WithCode) { return p.streamingProcessor.OpenStreamForAccount(ctx, account, streamType) } diff --git a/internal/processing/streaming/openstream.go b/internal/processing/streaming/openstream.go index dfad5398e..d4e4eef9f 100644 --- a/internal/processing/streaming/openstream.go +++ b/internal/processing/streaming/openstream.go @@ -9,9 +9,10 @@ import ( "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/id" + "github.com/superseriousbusiness/gotosocial/internal/stream" ) -func (p *processor) OpenStreamForAccount(ctx context.Context, account *gtsmodel.Account, streamType string) (*gtsmodel.Stream, gtserror.WithCode) { +func (p *processor) OpenStreamForAccount(ctx context.Context, account *gtsmodel.Account, streamType string) (*stream.Stream, gtserror.WithCode) { l := p.log.WithFields(logrus.Fields{ "func": "OpenStreamForAccount", "account": account.ID, @@ -25,10 +26,10 @@ func (p *processor) OpenStreamForAccount(ctx context.Context, account *gtsmodel. return nil, gtserror.NewErrorInternalError(fmt.Errorf("error generating stream id: %s", err)) } - thisStream := >smodel.Stream{ + thisStream := &stream.Stream{ ID: streamID, Type: streamType, - Messages: make(chan *gtsmodel.Message, 100), + Messages: make(chan *stream.Message, 100), Hangup: make(chan interface{}, 1), Connected: true, } @@ -37,8 +38,8 @@ func (p *processor) OpenStreamForAccount(ctx context.Context, account *gtsmodel. v, ok := p.streamMap.Load(account.ID) if !ok || v == nil { // there is no entry in the streamMap for this account yet, so make one and store it - streamsForAccount := >smodel.StreamsForAccount{ - Streams: []*gtsmodel.Stream{ + streamsForAccount := &stream.StreamsForAccount{ + Streams: []*stream.Stream{ thisStream, }, } @@ -46,7 +47,7 @@ func (p *processor) OpenStreamForAccount(ctx context.Context, account *gtsmodel. } else { // there is an entry in the streamMap for this account // parse the interface as a streamsForAccount - streamsForAccount, ok := v.(*gtsmodel.StreamsForAccount) + streamsForAccount, ok := v.(*stream.StreamsForAccount) if !ok { return nil, gtserror.NewErrorInternalError(errors.New("stream map error")) } @@ -63,7 +64,7 @@ func (p *processor) OpenStreamForAccount(ctx context.Context, account *gtsmodel. // waitToCloseStream waits until the hangup channel is closed for the given stream. // It then iterates through the map of streams stored by the processor, removes the stream from it, // and then closes the messages channel of the stream to indicate that the channel should no longer be read from. -func (p *processor) waitToCloseStream(account *gtsmodel.Account, thisStream *gtsmodel.Stream) { +func (p *processor) waitToCloseStream(account *gtsmodel.Account, thisStream *stream.Stream) { <-thisStream.Hangup // wait for a hangup message // lock the stream to prevent more messages being put in it while we work @@ -78,7 +79,7 @@ func (p *processor) waitToCloseStream(account *gtsmodel.Account, thisStream *gts if !ok || v == nil { return } - streamsForAccount, ok := v.(*gtsmodel.StreamsForAccount) + streamsForAccount, ok := v.(*stream.StreamsForAccount) if !ok { return } @@ -88,7 +89,7 @@ func (p *processor) waitToCloseStream(account *gtsmodel.Account, thisStream *gts defer streamsForAccount.Unlock() // put everything into modified streams *except* the stream we're removing - modifiedStreams := []*gtsmodel.Stream{} + modifiedStreams := []*stream.Stream{} for _, s := range streamsForAccount.Streams { if s.ID != thisStream.ID { modifiedStreams = append(modifiedStreams, s) diff --git a/internal/processing/streaming/streamdelete.go b/internal/processing/streaming/streamdelete.go index 2282c29ae..cd541bc57 100644 --- a/internal/processing/streaming/streamdelete.go +++ b/internal/processing/streaming/streamdelete.go @@ -4,7 +4,7 @@ import ( "fmt" "strings" - "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" + "github.com/superseriousbusiness/gotosocial/internal/stream" ) func (p *processor) StreamDelete(statusID string) error { @@ -20,7 +20,7 @@ func (p *processor) StreamDelete(statusID string) error { } // the value of the map should be a buncha streams - streamsForAccount, ok := v.(*gtsmodel.StreamsForAccount) + streamsForAccount, ok := v.(*stream.StreamsForAccount) if !ok { errs = append(errs, fmt.Sprintf("stream map error for account stream %s", accountID)) } @@ -28,13 +28,13 @@ func (p *processor) StreamDelete(statusID string) error { // lock the streams while we work on them streamsForAccount.Lock() defer streamsForAccount.Unlock() - for _, stream := range streamsForAccount.Streams { + for _, s := range streamsForAccount.Streams { // lock each individual stream as we work on it - stream.Lock() - defer stream.Unlock() - if stream.Connected { - stream.Messages <- >smodel.Message{ - Stream: []string{stream.Type}, + s.Lock() + defer s.Unlock() + if s.Connected { + s.Messages <- &stream.Message{ + Stream: []string{s.Type}, Event: "delete", Payload: statusID, } diff --git a/internal/processing/streaming/streaming.go b/internal/processing/streaming/streaming.go index f349a655a..610d4a9d2 100644 --- a/internal/processing/streaming/streaming.go +++ b/internal/processing/streaming/streaming.go @@ -11,6 +11,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/stream" "github.com/superseriousbusiness/gotosocial/internal/typeutils" "github.com/superseriousbusiness/gotosocial/internal/visibility" ) @@ -20,7 +21,7 @@ type Processor interface { // AuthorizeStreamingRequest returns an oauth2 token info in response to an access token query from the streaming API AuthorizeStreamingRequest(ctx context.Context, accessToken string) (*gtsmodel.Account, error) // OpenStreamForAccount returns a new Stream for the given account, which will contain a channel for passing messages back to the caller. - OpenStreamForAccount(ctx context.Context, account *gtsmodel.Account, streamType string) (*gtsmodel.Stream, gtserror.WithCode) + OpenStreamForAccount(ctx context.Context, account *gtsmodel.Account, streamType string) (*stream.Stream, gtserror.WithCode) // StreamStatusToAccount streams the given status to any open, appropriate streams belonging to the given account. StreamStatusToAccount(s *apimodel.Status, account *gtsmodel.Account) error // StreamNotificationToAccount streams the given notification to any open, appropriate streams belonging to the given account. diff --git a/internal/processing/streaming/streamnotification.go b/internal/processing/streaming/streamnotification.go index 24c8342ee..d8460874f 100644 --- a/internal/processing/streaming/streamnotification.go +++ b/internal/processing/streaming/streamnotification.go @@ -8,6 +8,7 @@ import ( "github.com/sirupsen/logrus" apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" + "github.com/superseriousbusiness/gotosocial/internal/stream" ) func (p *processor) StreamNotificationToAccount(n *apimodel.Notification, account *gtsmodel.Account) error { @@ -21,7 +22,7 @@ func (p *processor) StreamNotificationToAccount(n *apimodel.Notification, accoun return nil } - streamsForAccount, ok := v.(*gtsmodel.StreamsForAccount) + streamsForAccount, ok := v.(*stream.StreamsForAccount) if !ok { return errors.New("stream map error") } @@ -33,13 +34,13 @@ func (p *processor) StreamNotificationToAccount(n *apimodel.Notification, accoun streamsForAccount.Lock() defer streamsForAccount.Unlock() - for _, stream := range streamsForAccount.Streams { - stream.Lock() - defer stream.Unlock() - if stream.Connected { - l.Debugf("streaming notification to stream id %s", stream.ID) - stream.Messages <- >smodel.Message{ - Stream: []string{stream.Type}, + for _, s := range streamsForAccount.Streams { + s.Lock() + defer s.Unlock() + if s.Connected { + l.Debugf("streaming notification to stream id %s", s.ID) + s.Messages <- &stream.Message{ + Stream: []string{s.Type}, Event: "notification", Payload: string(notificationBytes), } diff --git a/internal/processing/streaming/streamstatus.go b/internal/processing/streaming/streamstatus.go index 8d026252d..f4d6b2629 100644 --- a/internal/processing/streaming/streamstatus.go +++ b/internal/processing/streaming/streamstatus.go @@ -8,6 +8,7 @@ import ( "github.com/sirupsen/logrus" apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" + "github.com/superseriousbusiness/gotosocial/internal/stream" ) func (p *processor) StreamStatusToAccount(s *apimodel.Status, account *gtsmodel.Account) error { @@ -21,7 +22,7 @@ func (p *processor) StreamStatusToAccount(s *apimodel.Status, account *gtsmodel. return nil } - streamsForAccount, ok := v.(*gtsmodel.StreamsForAccount) + streamsForAccount, ok := v.(*stream.StreamsForAccount) if !ok { return errors.New("stream map error") } @@ -33,13 +34,13 @@ func (p *processor) StreamStatusToAccount(s *apimodel.Status, account *gtsmodel. streamsForAccount.Lock() defer streamsForAccount.Unlock() - for _, stream := range streamsForAccount.Streams { - stream.Lock() - defer stream.Unlock() - if stream.Connected { - l.Debugf("streaming status to stream id %s", stream.ID) - stream.Messages <- >smodel.Message{ - Stream: []string{stream.Type}, + for _, s := range streamsForAccount.Streams { + s.Lock() + defer s.Unlock() + if s.Connected { + l.Debugf("streaming status to stream id %s", s.ID) + s.Messages <- &stream.Message{ + Stream: []string{s.Type}, Event: "update", Payload: string(statusBytes), } diff --git a/internal/regexes/regexes.go b/internal/regexes/regexes.go new file mode 100644 index 000000000..53446ff2c --- /dev/null +++ b/internal/regexes/regexes.go @@ -0,0 +1,136 @@ +/* + GoToSocial + Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package regexes + +import ( + "fmt" + "regexp" +) + +const ( + users = "users" + actors = "actors" + statuses = "statuses" + inbox = "inbox" + outbox = "outbox" + followers = "followers" + following = "following" + liked = "liked" + collections = "collections" + featured = "featured" + publicKey = "main-key" + follow = "follow" + update = "updates" + blocks = "blocks" +) + +const ( + maximumUsernameLength = 64 + maximumEmojiShortcodeLength = 30 + maximumHashtagLength = 30 +) + +var ( + mentionName = `^@(\w+)(?:@([a-zA-Z0-9_\-\.:]+)?)$` + // MentionName captures the username and domain part from a mention string + // such as @whatever_user@example.org, returning whatever_user and example.org (without the @ symbols) + MentionName = regexp.MustCompile(mentionName) + + // mention regex can be played around with here: https://regex101.com/r/qwM9D3/1 + mentionFinder = `(?:\B)(@\w+(?:@[a-zA-Z0-9_\-\.]+)?)(?:\B)?` + // MentionFinder extracts mentions from a piece of text. + MentionFinder = regexp.MustCompile(mentionFinder) + + // hashtag regex can be played with here: https://regex101.com/r/bPxeca/1 + hashtagFinder = fmt.Sprintf(`(?:^|\n|\s)(#[a-zA-Z0-9]{1,%d})(?:\b)`, maximumHashtagLength) + // HashtagFinder finds possible hashtags in a string. + // It returns just the string part of the hashtag, not the # symbol. + HashtagFinder = regexp.MustCompile(hashtagFinder) + + emojiShortcode = fmt.Sprintf(`\w{2,%d}`, maximumEmojiShortcodeLength) + // EmojiShortcode validates an emoji name. + EmojiShortcode = regexp.MustCompile(fmt.Sprintf("^%s$", emojiShortcode)) + + // emoji regex can be played with here: https://regex101.com/r/478XGM/1 + emojiFinderString = fmt.Sprintf(`(?:\B)?:(%s):(?:\B)?`, emojiShortcode) + // EmojiFinder extracts emoji strings from a piece of text. + EmojiFinder = regexp.MustCompile(emojiFinderString) + + // usernameString defines an acceptable username on this instance + usernameString = fmt.Sprintf(`[a-z0-9_]{2,%d}`, maximumUsernameLength) + // Username can be used to validate usernames of new signups + Username = regexp.MustCompile(fmt.Sprintf(`^%s$`, usernameString)) + + userPathString = fmt.Sprintf(`^?/%s/(%s)$`, users, usernameString) + // UserPath parses a path that validates and captures the username part from eg /users/example_username + UserPath = regexp.MustCompile(userPathString) + + publicKeyPath = fmt.Sprintf(`^?/%s/(%s)/%s`, users, usernameString, publicKey) + // PublicKeyPath parses a path that validates and captures the username part from eg /users/example_username/main-key + PublicKeyPath = regexp.MustCompile(publicKeyPath) + + inboxPath = fmt.Sprintf(`^/?%s/(%s)/%s$`, users, usernameString, inbox) + // InboxPath parses a path that validates and captures the username part from eg /users/example_username/inbox + InboxPath = regexp.MustCompile(inboxPath) + + outboxPath = fmt.Sprintf(`^/?%s/(%s)/%s$`, users, usernameString, outbox) + // OutboxPath parses a path that validates and captures the username part from eg /users/example_username/outbox + OutboxPath = regexp.MustCompile(outboxPath) + + actorPath = fmt.Sprintf(`^?/%s/(%s)$`, actors, usernameString) + // ActorPath parses a path that validates and captures the username part from eg /actors/example_username + ActorPath = regexp.MustCompile(actorPath) + + followersPath = fmt.Sprintf(`^/?%s/(%s)/%s$`, users, usernameString, followers) + // FollowersPath parses a path that validates and captures the username part from eg /users/example_username/followers + FollowersPath = regexp.MustCompile(followersPath) + + followingPath = fmt.Sprintf(`^/?%s/(%s)/%s$`, users, usernameString, following) + // FollowingPath parses a path that validates and captures the username part from eg /users/example_username/following + FollowingPath = regexp.MustCompile(followingPath) + + followPath = fmt.Sprintf(`^/?%s/(%s)/%s/(%s)$`, users, usernameString, follow, ulid) + // FollowPath parses a path that validates and captures the username part and the ulid part + // from eg /users/example_username/follow/01F7XT5JZW1WMVSW1KADS8PVDH + FollowPath = regexp.MustCompile(followPath) + + ulid = `[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{26}` + // ULID parses and validate a ULID. + ULID = regexp.MustCompile(fmt.Sprintf(`^%s$`, ulid)) + + likedPath = fmt.Sprintf(`^/?%s/(%s)/%s$`, users, usernameString, liked) + // LikedPath parses a path that validates and captures the username part from eg /users/example_username/liked + LikedPath = regexp.MustCompile(likedPath) + + likePath = fmt.Sprintf(`^/?%s/(%s)/%s/(%s)$`, users, usernameString, liked, ulid) + // LikePath parses a path that validates and captures the username part and the ulid part + // from eg /users/example_username/like/01F7XT5JZW1WMVSW1KADS8PVDH + LikePath = regexp.MustCompile(likePath) + + statusesPath = fmt.Sprintf(`^/?%s/(%s)/%s/(%s)$`, users, usernameString, statuses, ulid) + // StatusesPath parses a path that validates and captures the username part and the ulid part + // from eg /users/example_username/statuses/01F7XT5JZW1WMVSW1KADS8PVDH + // The regex can be played with here: https://regex101.com/r/G9zuxQ/1 + StatusesPath = regexp.MustCompile(statusesPath) + + blockPath = fmt.Sprintf(`^/?%s/(%s)/%s/(%s)$`, users, usernameString, blocks, ulid) + // BlockPath parses a path that validates and captures the username part and the ulid part + // from eg /users/example_username/blocks/01F7XT5JZW1WMVSW1KADS8PVDH + BlockPath = regexp.MustCompile(blockPath) +) diff --git a/internal/stream/stream.go b/internal/stream/stream.go new file mode 100644 index 000000000..9d1d27d72 --- /dev/null +++ b/internal/stream/stream.go @@ -0,0 +1,38 @@ +package stream + +import "sync" + +// StreamsForAccount is a wrapper for the multiple streams that one account can have running at the same time. +// TODO: put a limit on this +type StreamsForAccount struct { + // The currently held streams for this account + Streams []*Stream + // Mutex to lock/unlock when modifying the slice of streams. + sync.Mutex +} + +// Stream represents one open stream for a client. +type Stream struct { + // ID of this stream, generated during creation. + ID string + // Type of this stream: user/public/etc + Type string + // Channel of messages for the client to read from + Messages chan *Message + // Channel to close when the client drops away + Hangup chan interface{} + // Only put messages in the stream when Connected + Connected bool + // Mutex to lock/unlock when inserting messages, hanging up, changing the connected state etc. + sync.Mutex +} + +// Message represents one streamed message. +type Message struct { + // All the stream types this message should be delivered to. + Stream []string `json:"stream"` + // The event type of the message (update/delete/notification etc) + Event string `json:"event"` + // The actual payload of the message. In case of an update or notification, this will be a JSON string. + Payload string `json:"payload"` +} diff --git a/internal/text/common.go b/internal/text/common.go index a8d585a09..a3ec15e46 100644 --- a/internal/text/common.go +++ b/internal/text/common.go @@ -25,7 +25,7 @@ import ( "strings" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" - "github.com/superseriousbusiness/gotosocial/internal/util" + "github.com/superseriousbusiness/gotosocial/internal/regexes" ) // preformat contains some common logic for making a string ready for formatting, which should be used for all user-input text. @@ -61,7 +61,7 @@ func postformat(in string) string { } func (f *formatter) ReplaceTags(ctx context.Context, in string, tags []*gtsmodel.Tag) string { - return util.HashtagFinderRegex.ReplaceAllStringFunc(in, func(match string) string { + return regexes.HashtagFinder.ReplaceAllStringFunc(in, func(match string) string { // we have a match matchTrimmed := strings.TrimSpace(match) tagAsEntered := strings.Split(matchTrimmed, "#")[1] diff --git a/internal/transport/derefinstance.go b/internal/transport/derefinstance.go index 3d72d7581..673881b05 100644 --- a/internal/transport/derefinstance.go +++ b/internal/transport/derefinstance.go @@ -32,6 +32,7 @@ import ( "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/id" "github.com/superseriousbusiness/gotosocial/internal/util" + "github.com/superseriousbusiness/gotosocial/internal/validate" ) func (t *transport) DereferenceInstance(ctx context.Context, iri *url.URL) (*gtsmodel.Instance, error) { @@ -199,7 +200,7 @@ func dereferenceByNodeInfo(c context.Context, t *transport, iri *url.URL) (*gtsm if v, ok := i.(map[string]string); ok { // see if there's an email in the map if email, present := v["email"]; present { - if err := util.ValidateEmail(email); err == nil { + if err := validate.Email(email); err == nil { // valid email address contactEmail = email } diff --git a/internal/typeutils/internaltofrontend.go b/internal/typeutils/internaltofrontend.go index 03aa0c77b..7924e2185 100644 --- a/internal/typeutils/internaltofrontend.go +++ b/internal/typeutils/internaltofrontend.go @@ -227,7 +227,6 @@ func (c *converter) AppToMastoSensitive(ctx context.Context, a *gtsmodel.Applica RedirectURI: a.RedirectURI, ClientID: a.ClientID, ClientSecret: a.ClientSecret, - VapidKey: a.VapidKey, }, nil } diff --git a/internal/util/regexes.go b/internal/util/regexes.go deleted file mode 100644 index 36af9e7aa..000000000 --- a/internal/util/regexes.go +++ /dev/null @@ -1,114 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package util - -import ( - "fmt" - "regexp" -) - -const ( - maximumUsernameLength = 64 - maximumEmojiShortcodeLength = 30 - maximumHashtagLength = 30 -) - -var ( - mentionNameRegexString = `^@(\w+)(?:@([a-zA-Z0-9_\-\.:]+)?)$` - // mention name regex captures the username and domain part from a mention string - // such as @whatever_user@example.org, returning whatever_user and example.org (without the @ symbols) - mentionNameRegex = regexp.MustCompile(mentionNameRegexString) - - // mention regex can be played around with here: https://regex101.com/r/qwM9D3/1 - mentionFinderRegexString = `(?:\B)(@\w+(?:@[a-zA-Z0-9_\-\.]+)?)(?:\B)?` - mentionFinderRegex = regexp.MustCompile(mentionFinderRegexString) - - // hashtag regex can be played with here: https://regex101.com/r/bPxeca/1 - hashtagFinderRegexString = fmt.Sprintf(`(?:^|\n|\s)(#[a-zA-Z0-9]{1,%d})(?:\b)`, maximumHashtagLength) - // HashtagFinderRegex finds possible hashtags in a string. - // It returns just the string part of the hashtag, not the # symbol. - HashtagFinderRegex = regexp.MustCompile(hashtagFinderRegexString) - - emojiShortcodeRegexString = fmt.Sprintf(`\w{2,%d}`, maximumEmojiShortcodeLength) - emojiShortcodeValidationRegex = regexp.MustCompile(fmt.Sprintf("^%s$", emojiShortcodeRegexString)) - - // emoji regex can be played with here: https://regex101.com/r/478XGM/1 - emojiFinderRegexString = fmt.Sprintf(`(?:\B)?:(%s):(?:\B)?`, emojiShortcodeRegexString) - emojiFinderRegex = regexp.MustCompile(emojiFinderRegexString) - - // usernameRegexString defines an acceptable username on this instance - usernameRegexString = fmt.Sprintf(`[a-z0-9_]{2,%d}`, maximumUsernameLength) - // usernameValidationRegex can be used to validate usernames of new signups - usernameValidationRegex = regexp.MustCompile(fmt.Sprintf(`^%s$`, usernameRegexString)) - - userPathRegexString = fmt.Sprintf(`^?/%s/(%s)$`, UsersPath, usernameRegexString) - // userPathRegex parses a path that validates and captures the username part from eg /users/example_username - userPathRegex = regexp.MustCompile(userPathRegexString) - - userPublicKeyPathRegexString = fmt.Sprintf(`^?/%s/(%s)/%s`, UsersPath, usernameRegexString, PublicKeyPath) - userPublicKeyPathRegex = regexp.MustCompile(userPublicKeyPathRegexString) - - inboxPathRegexString = fmt.Sprintf(`^/?%s/(%s)/%s$`, UsersPath, usernameRegexString, InboxPath) - // inboxPathRegex parses a path that validates and captures the username part from eg /users/example_username/inbox - inboxPathRegex = regexp.MustCompile(inboxPathRegexString) - - outboxPathRegexString = fmt.Sprintf(`^/?%s/(%s)/%s$`, UsersPath, usernameRegexString, OutboxPath) - // outboxPathRegex parses a path that validates and captures the username part from eg /users/example_username/outbox - outboxPathRegex = regexp.MustCompile(outboxPathRegexString) - - actorPathRegexString = fmt.Sprintf(`^?/%s/(%s)$`, ActorsPath, usernameRegexString) - // actorPathRegex parses a path that validates and captures the username part from eg /actors/example_username - actorPathRegex = regexp.MustCompile(actorPathRegexString) - - followersPathRegexString = fmt.Sprintf(`^/?%s/(%s)/%s$`, UsersPath, usernameRegexString, FollowersPath) - // followersPathRegex parses a path that validates and captures the username part from eg /users/example_username/followers - followersPathRegex = regexp.MustCompile(followersPathRegexString) - - followingPathRegexString = fmt.Sprintf(`^/?%s/(%s)/%s$`, UsersPath, usernameRegexString, FollowingPath) - // followingPathRegex parses a path that validates and captures the username part from eg /users/example_username/following - followingPathRegex = regexp.MustCompile(followingPathRegexString) - - followPathRegexString = fmt.Sprintf(`^/?%s/(%s)/%s/(%s)$`, UsersPath, usernameRegexString, FollowPath, ulidRegexString) - // followPathRegex parses a path that validates and captures the username part and the ulid part - // from eg /users/example_username/follow/01F7XT5JZW1WMVSW1KADS8PVDH - followPathRegex = regexp.MustCompile(followPathRegexString) - - ulidRegexString = `[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{26}` - ulidRegex = regexp.MustCompile(fmt.Sprintf(`^%s$`, ulidRegexString)) - - likedPathRegexString = fmt.Sprintf(`^/?%s/(%s)/%s$`, UsersPath, usernameRegexString, LikedPath) - // likedPathRegex parses a path that validates and captures the username part from eg /users/example_username/liked - likedPathRegex = regexp.MustCompile(likedPathRegexString) - - likePathRegexString = fmt.Sprintf(`^/?%s/(%s)/%s/(%s)$`, UsersPath, usernameRegexString, LikedPath, ulidRegexString) - // likePathRegex parses a path that validates and captures the username part and the ulid part - // from eg /users/example_username/like/01F7XT5JZW1WMVSW1KADS8PVDH - likePathRegex = regexp.MustCompile(likePathRegexString) - - statusesPathRegexString = fmt.Sprintf(`^/?%s/(%s)/%s/(%s)$`, UsersPath, usernameRegexString, StatusesPath, ulidRegexString) - // statusesPathRegex parses a path that validates and captures the username part and the ulid part - // from eg /users/example_username/statuses/01F7XT5JZW1WMVSW1KADS8PVDH - // The regex can be played with here: https://regex101.com/r/G9zuxQ/1 - statusesPathRegex = regexp.MustCompile(statusesPathRegexString) - - blockPathRegexString = fmt.Sprintf(`^/?%s/(%s)/%s/(%s)$`, UsersPath, usernameRegexString, BlocksPath, ulidRegexString) - // blockPathRegex parses a path that validates and captures the username part and the ulid part - // from eg /users/example_username/blocks/01F7XT5JZW1WMVSW1KADS8PVDH - blockPathRegex = regexp.MustCompile(blockPathRegexString) -) diff --git a/internal/util/statustools.go b/internal/util/statustools.go index 4a89e60f6..ca18577b0 100644 --- a/internal/util/statustools.go +++ b/internal/util/statustools.go @@ -21,6 +21,8 @@ package util import ( "fmt" "strings" + + "github.com/superseriousbusiness/gotosocial/internal/regexes" ) // DeriveMentionsFromStatus takes a plaintext (ie., not html-formatted) status, @@ -31,7 +33,7 @@ import ( // or the form "@username" for local users. func DeriveMentionsFromStatus(status string) []string { mentionedAccounts := []string{} - for _, m := range mentionFinderRegex.FindAllStringSubmatch(status, -1) { + for _, m := range regexes.MentionFinder.FindAllStringSubmatch(status, -1) { mentionedAccounts = append(mentionedAccounts, m[1]) } return UniqueStrings(mentionedAccounts) @@ -43,7 +45,7 @@ func DeriveMentionsFromStatus(status string) []string { // tags will be lowered, for consistency. func DeriveHashtagsFromStatus(status string) []string { tags := []string{} - for _, m := range HashtagFinderRegex.FindAllStringSubmatch(status, -1) { + for _, m := range regexes.HashtagFinder.FindAllStringSubmatch(status, -1) { tags = append(tags, strings.TrimPrefix(m[1], "#")) } return UniqueStrings(tags) @@ -54,7 +56,7 @@ func DeriveHashtagsFromStatus(status string) []string { // used in that status, without the surround ::. func DeriveEmojisFromStatus(status string) []string { emojis := []string{} - for _, m := range emojiFinderRegex.FindAllStringSubmatch(status, -1) { + for _, m := range regexes.EmojiFinder.FindAllStringSubmatch(status, -1) { emojis = append(emojis, m[1]) } return UniqueStrings(emojis) @@ -65,7 +67,7 @@ func DeriveEmojisFromStatus(status string) []string { // // If nothing is matched, it will return an error. func ExtractMentionParts(mention string) (username, domain string, err error) { - matches := mentionNameRegex.FindStringSubmatch(mention) + matches := regexes.MentionName.FindStringSubmatch(mention) if matches == nil || len(matches) != 3 { err = fmt.Errorf("could't match mention %s", mention) return @@ -77,5 +79,5 @@ func ExtractMentionParts(mention string) (username, domain string, err error) { // IsMention returns true if the passed string looks like @whatever@example.org func IsMention(mention string) bool { - return mentionNameRegex.MatchString(strings.ToLower(mention)) + return regexes.MentionName.MatchString(strings.ToLower(mention)) } diff --git a/internal/util/uri.go b/internal/util/uri.go index 370b2fa6f..91f523a4d 100644 --- a/internal/util/uri.go +++ b/internal/util/uri.go @@ -21,6 +21,8 @@ package util import ( "fmt" "net/url" + + "github.com/superseriousbusiness/gotosocial/internal/regexes" ) const ( @@ -169,67 +171,67 @@ func GenerateURIsForAccount(username string, protocol string, host string) *User // IsUserPath returns true if the given URL path corresponds to eg /users/example_username func IsUserPath(id *url.URL) bool { - return userPathRegex.MatchString(id.Path) + return regexes.UserPath.MatchString(id.Path) } // IsInboxPath returns true if the given URL path corresponds to eg /users/example_username/inbox func IsInboxPath(id *url.URL) bool { - return inboxPathRegex.MatchString(id.Path) + return regexes.InboxPath.MatchString(id.Path) } // IsOutboxPath returns true if the given URL path corresponds to eg /users/example_username/outbox func IsOutboxPath(id *url.URL) bool { - return outboxPathRegex.MatchString(id.Path) + return regexes.OutboxPath.MatchString(id.Path) } // IsInstanceActorPath returns true if the given URL path corresponds to eg /actors/example_username func IsInstanceActorPath(id *url.URL) bool { - return actorPathRegex.MatchString(id.Path) + return regexes.ActorPath.MatchString(id.Path) } // IsFollowersPath returns true if the given URL path corresponds to eg /users/example_username/followers func IsFollowersPath(id *url.URL) bool { - return followersPathRegex.MatchString(id.Path) + return regexes.FollowersPath.MatchString(id.Path) } // IsFollowingPath returns true if the given URL path corresponds to eg /users/example_username/following func IsFollowingPath(id *url.URL) bool { - return followingPathRegex.MatchString(id.Path) + return regexes.FollowingPath.MatchString(id.Path) } // IsFollowPath returns true if the given URL path corresponds to eg /users/example_username/follow/SOME_ULID_OF_A_FOLLOW func IsFollowPath(id *url.URL) bool { - return followPathRegex.MatchString(id.Path) + return regexes.FollowPath.MatchString(id.Path) } // IsLikedPath returns true if the given URL path corresponds to eg /users/example_username/liked func IsLikedPath(id *url.URL) bool { - return likedPathRegex.MatchString(id.Path) + return regexes.LikedPath.MatchString(id.Path) } // IsLikePath returns true if the given URL path corresponds to eg /users/example_username/liked/SOME_ULID_OF_A_STATUS func IsLikePath(id *url.URL) bool { - return likePathRegex.MatchString(id.Path) + return regexes.LikePath.MatchString(id.Path) } // IsStatusesPath returns true if the given URL path corresponds to eg /users/example_username/statuses/SOME_ULID_OF_A_STATUS func IsStatusesPath(id *url.URL) bool { - return statusesPathRegex.MatchString(id.Path) + return regexes.StatusesPath.MatchString(id.Path) } // IsPublicKeyPath returns true if the given URL path corresponds to eg /users/example_username/main-key func IsPublicKeyPath(id *url.URL) bool { - return userPublicKeyPathRegex.MatchString(id.Path) + return regexes.PublicKeyPath.MatchString(id.Path) } // IsBlockPath returns true if the given URL path corresponds to eg /users/example_username/blocks/SOME_ULID_OF_A_BLOCK func IsBlockPath(id *url.URL) bool { - return blockPathRegex.MatchString(id.Path) + return regexes.BlockPath.MatchString(id.Path) } // ParseStatusesPath returns the username and ulid from a path such as /users/example_username/statuses/SOME_ULID_OF_A_STATUS func ParseStatusesPath(id *url.URL) (username string, ulid string, err error) { - matches := statusesPathRegex.FindStringSubmatch(id.Path) + matches := regexes.StatusesPath.FindStringSubmatch(id.Path) if len(matches) != 3 { err = fmt.Errorf("expected 3 matches but matches length was %d", len(matches)) return @@ -241,7 +243,7 @@ func ParseStatusesPath(id *url.URL) (username string, ulid string, err error) { // ParseUserPath returns the username from a path such as /users/example_username func ParseUserPath(id *url.URL) (username string, err error) { - matches := userPathRegex.FindStringSubmatch(id.Path) + matches := regexes.UserPath.FindStringSubmatch(id.Path) if len(matches) != 2 { err = fmt.Errorf("expected 2 matches but matches length was %d", len(matches)) return @@ -252,7 +254,7 @@ func ParseUserPath(id *url.URL) (username string, err error) { // ParseInboxPath returns the username from a path such as /users/example_username/inbox func ParseInboxPath(id *url.URL) (username string, err error) { - matches := inboxPathRegex.FindStringSubmatch(id.Path) + matches := regexes.InboxPath.FindStringSubmatch(id.Path) if len(matches) != 2 { err = fmt.Errorf("expected 2 matches but matches length was %d", len(matches)) return @@ -263,7 +265,7 @@ func ParseInboxPath(id *url.URL) (username string, err error) { // ParseOutboxPath returns the username from a path such as /users/example_username/outbox func ParseOutboxPath(id *url.URL) (username string, err error) { - matches := outboxPathRegex.FindStringSubmatch(id.Path) + matches := regexes.OutboxPath.FindStringSubmatch(id.Path) if len(matches) != 2 { err = fmt.Errorf("expected 2 matches but matches length was %d", len(matches)) return @@ -274,7 +276,7 @@ func ParseOutboxPath(id *url.URL) (username string, err error) { // ParseFollowersPath returns the username from a path such as /users/example_username/followers func ParseFollowersPath(id *url.URL) (username string, err error) { - matches := followersPathRegex.FindStringSubmatch(id.Path) + matches := regexes.FollowersPath.FindStringSubmatch(id.Path) if len(matches) != 2 { err = fmt.Errorf("expected 2 matches but matches length was %d", len(matches)) return @@ -285,7 +287,7 @@ func ParseFollowersPath(id *url.URL) (username string, err error) { // ParseFollowingPath returns the username from a path such as /users/example_username/following func ParseFollowingPath(id *url.URL) (username string, err error) { - matches := followingPathRegex.FindStringSubmatch(id.Path) + matches := regexes.FollowingPath.FindStringSubmatch(id.Path) if len(matches) != 2 { err = fmt.Errorf("expected 2 matches but matches length was %d", len(matches)) return @@ -296,7 +298,7 @@ func ParseFollowingPath(id *url.URL) (username string, err error) { // ParseLikedPath returns the username and ulid from a path such as /users/example_username/liked/SOME_ULID_OF_A_STATUS func ParseLikedPath(id *url.URL) (username string, ulid string, err error) { - matches := likePathRegex.FindStringSubmatch(id.Path) + matches := regexes.LikePath.FindStringSubmatch(id.Path) if len(matches) != 3 { err = fmt.Errorf("expected 3 matches but matches length was %d", len(matches)) return @@ -308,7 +310,7 @@ func ParseLikedPath(id *url.URL) (username string, ulid string, err error) { // ParseBlockPath returns the username and ulid from a path such as /users/example_username/blocks/SOME_ULID_OF_A_BLOCK func ParseBlockPath(id *url.URL) (username string, ulid string, err error) { - matches := blockPathRegex.FindStringSubmatch(id.Path) + matches := regexes.BlockPath.FindStringSubmatch(id.Path) if len(matches) != 3 { err = fmt.Errorf("expected 3 matches but matches length was %d", len(matches)) return diff --git a/internal/util/validation.go b/internal/util/validation.go deleted file mode 100644 index aa25ccd16..000000000 --- a/internal/util/validation.go +++ /dev/null @@ -1,178 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package util - -import ( - "errors" - "fmt" - "net/mail" - - pwv "github.com/wagslane/go-password-validator" - "golang.org/x/text/language" -) - -const ( - maximumPasswordLength = 64 - minimumPasswordEntropy = 60 // dictates password strength. See https://github.com/wagslane/go-password-validator - minimumReasonLength = 40 - maximumReasonLength = 500 - maximumSiteTitleLength = 40 - maximumShortDescriptionLength = 500 - maximumDescriptionLength = 5000 - maximumSiteTermsLength = 5000 -) - -// ValidateNewPassword returns an error if the given password is not sufficiently strong, or nil if it's ok. -func ValidateNewPassword(password string) error { - if password == "" { - return errors.New("no password provided") - } - - if len(password) > maximumPasswordLength { - return fmt.Errorf("password should be no more than %d chars", maximumPasswordLength) - } - - return pwv.Validate(password, minimumPasswordEntropy) -} - -// ValidateUsername makes sure that a given username is valid (ie., letters, numbers, underscores, check length). -// Returns an error if not. -func ValidateUsername(username string) error { - if username == "" { - return errors.New("no username provided") - } - - if !usernameValidationRegex.MatchString(username) { - return fmt.Errorf("given username %s was invalid: must contain only lowercase letters, numbers, and underscores, max %d characters", username, maximumUsernameLength) - } - - return nil -} - -// ValidateEmail makes sure that a given email address is a valid address. -// Returns an error if not. -func ValidateEmail(email string) error { - if email == "" { - return errors.New("no email provided") - } - - _, err := mail.ParseAddress(email) - return err -} - -// ValidateLanguage 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 ValidateLanguage(lang string) error { - if lang == "" { - return errors.New("no language provided") - } - _, err := language.ParseBase(lang) - return err -} - -// ValidateSignUpReason checks that a sufficient reason is given for a server signup request -func ValidateSignUpReason(reason string, reasonRequired bool) error { - if !reasonRequired { - // we don't care! - // we're not going to do anything with this text anyway if no reason is required - return nil - } - - if reason == "" { - return errors.New("no reason provided") - } - - if len(reason) < minimumReasonLength { - return fmt.Errorf("reason should be at least %d chars but '%s' was %d", minimumReasonLength, reason, len(reason)) - } - - if len(reason) > maximumReasonLength { - return fmt.Errorf("reason should be no more than %d chars but given reason was %d", maximumReasonLength, len(reason)) - } - return nil -} - -// ValidateDisplayName checks that a requested display name is valid -func ValidateDisplayName(displayName string) error { - // TODO: add some validation logic here -- length, characters, etc - return nil -} - -// ValidateNote checks that a given profile/account note/bio is valid -func ValidateNote(note string) error { - // TODO: add some validation logic here -- length, characters, etc - return nil -} - -// ValidatePrivacy checks that the desired privacy setting is valid -func ValidatePrivacy(privacy string) error { - // TODO: add some validation logic here -- length, characters, etc - return nil -} - -// ValidateEmojiShortcode just runs the given shortcode through the regular expression -// for emoji shortcodes, to figure out whether it's a valid shortcode, ie., 2-30 characters, -// lowercase a-z, numbers, and underscores. -func ValidateEmojiShortcode(shortcode string) error { - if !emojiShortcodeValidationRegex.MatchString(shortcode) { - return fmt.Errorf("shortcode %s did not pass validation, must be between 2 and 30 characters, lowercase letters, numbers, and underscores only", shortcode) - } - return nil -} - -// ValidateSiteTitle ensures that the given site title is within spec. -func ValidateSiteTitle(siteTitle string) error { - if len(siteTitle) > maximumSiteTitleLength { - return fmt.Errorf("site title should be no more than %d chars but given title was %d", maximumSiteTitleLength, len(siteTitle)) - } - - return nil -} - -// ValidateSiteShortDescription ensures that the given site short description is within spec. -func ValidateSiteShortDescription(d string) error { - if len(d) > maximumShortDescriptionLength { - return fmt.Errorf("short description should be no more than %d chars but given description was %d", maximumShortDescriptionLength, len(d)) - } - - return nil -} - -// ValidateSiteDescription ensures that the given site description is within spec. -func ValidateSiteDescription(d string) error { - if len(d) > maximumDescriptionLength { - return fmt.Errorf("description should be no more than %d chars but given description was %d", maximumDescriptionLength, len(d)) - } - - return nil -} - -// ValidateSiteTerms ensures that the given site terms string is within spec. -func ValidateSiteTerms(t string) error { - if len(t) > maximumSiteTermsLength { - return fmt.Errorf("terms should be no more than %d chars but given terms was %d", maximumSiteTermsLength, len(t)) - } - - return nil -} - -// ValidateULID returns true if the passed string is a valid ULID. -func ValidateULID(i string) bool { - return ulidRegex.MatchString(i) -} diff --git a/internal/util/validation_test.go b/internal/util/validation_test.go deleted file mode 100644 index 639a89bbd..000000000 --- a/internal/util/validation_test.go +++ /dev/null @@ -1,283 +0,0 @@ -/* - GoToSocial - Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -package util_test - -import ( - "errors" - "fmt" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/suite" - "github.com/superseriousbusiness/gotosocial/internal/util" -) - -type ValidationTestSuite struct { - suite.Suite -} - -func (suite *ValidationTestSuite) TestCheckPasswordStrength() { - empty := "" - terriblePassword := "password" - weakPassword := "OKPassword" - shortPassword := "Ok12" - specialPassword := "Ok12%" - longPassword := "thisisafuckinglongpasswordbutnospecialchars" - tooLong := "Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Quisque a enim nibh. Vestibulum bibendum leo ac porttitor auctor." - strongPassword := "3dX5@Zc%mV*W2MBNEy$@" - var err error - - err = util.ValidateNewPassword(empty) - if assert.Error(suite.T(), err) { - assert.Equal(suite.T(), errors.New("no password provided"), err) - } - - err = util.ValidateNewPassword(terriblePassword) - if assert.Error(suite.T(), err) { - assert.Equal(suite.T(), errors.New("insecure password, try including more special characters, using uppercase letters, using numbers or using a longer password"), err) - } - - err = util.ValidateNewPassword(weakPassword) - if assert.Error(suite.T(), err) { - assert.Equal(suite.T(), errors.New("insecure password, try including more special characters, using numbers or using a longer password"), err) - } - - err = util.ValidateNewPassword(shortPassword) - if assert.Error(suite.T(), err) { - assert.Equal(suite.T(), errors.New("insecure password, try including more special characters or using a longer password"), err) - } - - err = util.ValidateNewPassword(specialPassword) - if assert.Error(suite.T(), err) { - assert.Equal(suite.T(), errors.New("insecure password, try including more special characters or using a longer password"), err) - } - - err = util.ValidateNewPassword(longPassword) - if assert.NoError(suite.T(), err) { - assert.Equal(suite.T(), nil, err) - } - - err = util.ValidateNewPassword(tooLong) - if assert.Error(suite.T(), err) { - assert.Equal(suite.T(), errors.New("password should be no more than 64 chars"), err) - } - - err = util.ValidateNewPassword(strongPassword) - if assert.NoError(suite.T(), err) { - assert.Equal(suite.T(), nil, err) - } -} - -func (suite *ValidationTestSuite) TestValidateUsername() { - empty := "" - tooLong := "holycrapthisisthelongestusernameiveeverseeninmylifethatstoomuchman" - withSpaces := "this username has spaces in it" - weirdChars := "thisusername&&&&&&&istooweird!!" - leadingSpace := " see_that_leading_space" - trailingSpace := "thisusername_ends_with_a_space " - newlines := "this_is\n_almost_ok" - goodUsername := "this_is_a_good_username" - var err error - - err = util.ValidateUsername(empty) - if assert.Error(suite.T(), err) { - assert.Equal(suite.T(), errors.New("no username provided"), err) - } - - err = util.ValidateUsername(tooLong) - if assert.Error(suite.T(), err) { - assert.Equal(suite.T(), fmt.Errorf("given username %s was invalid: must contain only lowercase letters, numbers, and underscores, max 64 characters", tooLong), err) - } - - err = util.ValidateUsername(withSpaces) - if assert.Error(suite.T(), err) { - assert.Equal(suite.T(), fmt.Errorf("given username %s was invalid: must contain only lowercase letters, numbers, and underscores, max 64 characters", withSpaces), err) - } - - err = util.ValidateUsername(weirdChars) - if assert.Error(suite.T(), err) { - assert.Equal(suite.T(), fmt.Errorf("given username %s was invalid: must contain only lowercase letters, numbers, and underscores, max 64 characters", weirdChars), err) - } - - err = util.ValidateUsername(leadingSpace) - if assert.Error(suite.T(), err) { - assert.Equal(suite.T(), fmt.Errorf("given username %s was invalid: must contain only lowercase letters, numbers, and underscores, max 64 characters", leadingSpace), err) - } - - err = util.ValidateUsername(trailingSpace) - if assert.Error(suite.T(), err) { - assert.Equal(suite.T(), fmt.Errorf("given username %s was invalid: must contain only lowercase letters, numbers, and underscores, max 64 characters", trailingSpace), err) - } - - err = util.ValidateUsername(newlines) - if assert.Error(suite.T(), err) { - assert.Equal(suite.T(), fmt.Errorf("given username %s was invalid: must contain only lowercase letters, numbers, and underscores, max 64 characters", newlines), err) - } - - err = util.ValidateUsername(goodUsername) - if assert.NoError(suite.T(), err) { - assert.Equal(suite.T(), nil, err) - } -} - -func (suite *ValidationTestSuite) TestValidateEmail() { - empty := "" - notAnEmailAddress := "this-is-no-email-address!" - almostAnEmailAddress := "@thisisalmostan@email.address" - aWebsite := "https://thisisawebsite.com" - emailAddress := "thisis.actually@anemail.address" - var err error - - err = util.ValidateEmail(empty) - if assert.Error(suite.T(), err) { - assert.Equal(suite.T(), errors.New("no email provided"), err) - } - - err = util.ValidateEmail(notAnEmailAddress) - if assert.Error(suite.T(), err) { - assert.Equal(suite.T(), errors.New("mail: missing '@' or angle-addr"), err) - } - - err = util.ValidateEmail(almostAnEmailAddress) - if assert.Error(suite.T(), err) { - assert.Equal(suite.T(), errors.New("mail: no angle-addr"), err) - } - - err = util.ValidateEmail(aWebsite) - if assert.Error(suite.T(), err) { - assert.Equal(suite.T(), errors.New("mail: missing '@' or angle-addr"), err) - } - - err = util.ValidateEmail(emailAddress) - if assert.NoError(suite.T(), err) { - assert.Equal(suite.T(), nil, err) - } -} - -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 = util.ValidateLanguage(empty) - if assert.Error(suite.T(), err) { - assert.Equal(suite.T(), errors.New("no language provided"), err) - } - - err = util.ValidateLanguage(notALanguage) - if assert.Error(suite.T(), err) { - assert.Equal(suite.T(), errors.New("language: tag is not well-formed"), err) - } - - err = util.ValidateLanguage(english) - if assert.NoError(suite.T(), err) { - assert.Equal(suite.T(), nil, err) - } - - err = util.ValidateLanguage(capitalEnglish) - if assert.NoError(suite.T(), err) { - assert.Equal(suite.T(), nil, err) - } - - err = util.ValidateLanguage(arabic3Letters) - if assert.NoError(suite.T(), err) { - assert.Equal(suite.T(), nil, err) - } - - err = util.ValidateLanguage(mixedCapsEnglish) - if assert.NoError(suite.T(), err) { - assert.Equal(suite.T(), nil, err) - } - - err = util.ValidateLanguage(englishUS) - if assert.Error(suite.T(), err) { - assert.Equal(suite.T(), errors.New("language: tag is not well-formed"), err) - } - - err = util.ValidateLanguage(dutch) - if assert.NoError(suite.T(), err) { - assert.Equal(suite.T(), nil, err) - } - - err = util.ValidateLanguage(german) - if assert.NoError(suite.T(), err) { - assert.Equal(suite.T(), nil, err) - } -} - -func (suite *ValidationTestSuite) TestValidateReason() { - empty := "" - badReason := "because" - goodReason := "to smash the state and destroy capitalism ultimately and completely" - tooLong := "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris auctor mollis viverra. Maecenas maximus mollis sem, nec fermentum velit consectetur non. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Quisque a enim nibh. Vestibulum bibendum leo ac porttitor auctor. Curabitur velit tellus, facilisis vitae lorem a, ullamcorper efficitur leo. Sed a auctor tortor. Sed ut finibus ante, sit amet laoreet sapien. Donec ullamcorper tellus a nibh sodales vulputate. Donec id dolor eu odio mollis bibendum. Pellentesque habitant morbi tristique senectus et netus at." - var err error - - // check with no reason required - err = util.ValidateSignUpReason(empty, false) - if assert.NoError(suite.T(), err) { - assert.Equal(suite.T(), nil, err) - } - - err = util.ValidateSignUpReason(badReason, false) - if assert.NoError(suite.T(), err) { - assert.Equal(suite.T(), nil, err) - } - - err = util.ValidateSignUpReason(tooLong, false) - if assert.NoError(suite.T(), err) { - assert.Equal(suite.T(), nil, err) - } - - err = util.ValidateSignUpReason(goodReason, false) - if assert.NoError(suite.T(), err) { - assert.Equal(suite.T(), nil, err) - } - - // check with reason required - err = util.ValidateSignUpReason(empty, true) - if assert.Error(suite.T(), err) { - assert.Equal(suite.T(), errors.New("no reason provided"), err) - } - - err = util.ValidateSignUpReason(badReason, true) - if assert.Error(suite.T(), err) { - assert.Equal(suite.T(), errors.New("reason should be at least 40 chars but 'because' was 7"), err) - } - - err = util.ValidateSignUpReason(tooLong, true) - if assert.Error(suite.T(), err) { - assert.Equal(suite.T(), errors.New("reason should be no more than 500 chars but given reason was 600"), err) - } - - err = util.ValidateSignUpReason(goodReason, true) - if assert.NoError(suite.T(), err) { - assert.Equal(suite.T(), nil, err) - } -} - -func TestValidationTestSuite(t *testing.T) { - suite.Run(t, new(ValidationTestSuite)) -} diff --git a/internal/validate/block_test.go b/internal/validate/block_test.go new file mode 100644 index 000000000..0b3293fb5 --- /dev/null +++ b/internal/validate/block_test.go @@ -0,0 +1,116 @@ +/* + GoToSocial + Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package validate_test + +import ( + "testing" + "time" + + "github.com/stretchr/testify/suite" + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" + "github.com/superseriousbusiness/gotosocial/internal/validate" +) + +func happyBlock() *gtsmodel.Block { + return >smodel.Block{ + ID: "01FE91RJR88PSEEE30EV35QR8N", + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + URI: "https://example.org/accounts/someone/blocks/01FE91RJR88PSEEE30EV35QR8N", + AccountID: "01FEED79PRMVWPRMFHFQM8MJQN", + Account: nil, + TargetAccountID: "01FEEDMF6C0QD589MRK7919Z0R", + TargetAccount: nil, + } +} + +type BlockValidateTestSuite struct { + suite.Suite +} + +func (suite *BlockValidateTestSuite) TestValidateBlockHappyPath() { + // no problem here + d := happyBlock() + err := validate.Struct(*d) + suite.NoError(err) +} + +func (suite *BlockValidateTestSuite) TestValidateBlockBadID() { + d := happyBlock() + + d.ID = "" + err := validate.Struct(*d) + suite.EqualError(err, "Key: 'Block.ID' Error:Field validation for 'ID' failed on the 'required' tag") + + d.ID = "01FE96W293ZPRG9FQQP48HK8N001FE96W32AT24VYBGM12WN3GKB" + err = validate.Struct(*d) + suite.EqualError(err, "Key: 'Block.ID' Error:Field validation for 'ID' failed on the 'ulid' tag") +} + +func (suite *BlockValidateTestSuite) TestValidateBlockNoCreatedAt() { + d := happyBlock() + + d.CreatedAt = time.Time{} + err := validate.Struct(*d) + suite.NoError(err) +} + +func (suite *BlockValidateTestSuite) TestValidateBlockCreatedByAccountID() { + d := happyBlock() + + d.AccountID = "" + err := validate.Struct(*d) + suite.EqualError(err, "Key: 'Block.AccountID' Error:Field validation for 'AccountID' failed on the 'required' tag") + + d.AccountID = "this-is-not-a-valid-ulid" + err = validate.Struct(*d) + suite.EqualError(err, "Key: 'Block.AccountID' Error:Field validation for 'AccountID' failed on the 'ulid' tag") +} + +func (suite *BlockValidateTestSuite) TestValidateBlockTargetAccountID() { + d := happyBlock() + + d.TargetAccountID = "invalid-ulid" + err := validate.Struct(*d) + suite.EqualError(err, "Key: 'Block.TargetAccountID' Error:Field validation for 'TargetAccountID' failed on the 'ulid' tag") + + d.TargetAccountID = "01FEEDHX4G7EGHF5GD9E82Y51Q" + err = validate.Struct(*d) + suite.NoError(err) + + d.TargetAccountID = "" + err = validate.Struct(*d) + suite.EqualError(err, "Key: 'Block.TargetAccountID' Error:Field validation for 'TargetAccountID' failed on the 'required' tag") +} + +func (suite *BlockValidateTestSuite) TestValidateBlockURI() { + d := happyBlock() + + d.URI = "invalid-uri" + err := validate.Struct(*d) + suite.EqualError(err, "Key: 'Block.URI' Error:Field validation for 'URI' failed on the 'url' tag") + + d.URI = "" + err = validate.Struct(*d) + 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)) +} diff --git a/internal/validate/domainblock_test.go b/internal/validate/domainblock_test.go new file mode 100644 index 000000000..0ce826f12 --- /dev/null +++ b/internal/validate/domainblock_test.go @@ -0,0 +1,122 @@ +/* + GoToSocial + Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package validate_test + +import ( + "testing" + "time" + + "github.com/stretchr/testify/suite" + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" + "github.com/superseriousbusiness/gotosocial/internal/validate" +) + +func happyDomainBlock() *gtsmodel.DomainBlock { + return >smodel.DomainBlock{ + ID: "01FE91RJR88PSEEE30EV35QR8N", + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + Domain: "baddudes.suck", + CreatedByAccountID: "01FEED79PRMVWPRMFHFQM8MJQN", + PrivateComment: "we don't like em", + PublicComment: "poo poo dudes", + Obfuscate: false, + 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)) +} diff --git a/internal/validate/emaildomainblock_test.go b/internal/validate/emaildomainblock_test.go new file mode 100644 index 000000000..26ba08615 --- /dev/null +++ b/internal/validate/emaildomainblock_test.go @@ -0,0 +1,97 @@ +/* + GoToSocial + Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package validate_test + +import ( + "testing" + "time" + + "github.com/stretchr/testify/suite" + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" + "github.com/superseriousbusiness/gotosocial/internal/validate" +) + +func happyEmailDomainBlock() *gtsmodel.EmailDomainBlock { + return >smodel.EmailDomainBlock{ + ID: "01FE91RJR88PSEEE30EV35QR8N", + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + Domain: "baddudes.suck", + CreatedByAccountID: "01FEED79PRMVWPRMFHFQM8MJQN", + } +} + +type EmailDomainBlockValidateTestSuite struct { + suite.Suite +} + +func (suite *EmailDomainBlockValidateTestSuite) TestValidateEmailDomainBlockHappyPath() { + // no problem here + e := happyEmailDomainBlock() + err := validate.Struct(*e) + suite.NoError(err) +} + +func (suite *EmailDomainBlockValidateTestSuite) TestValidateEmailDomainBlockBadID() { + e := happyEmailDomainBlock() + + e.ID = "" + err := validate.Struct(*e) + suite.EqualError(err, "Key: 'EmailDomainBlock.ID' Error:Field validation for 'ID' failed on the 'required' tag") + + e.ID = "01FE96W293ZPRG9FQQP48HK8N001FE96W32AT24VYBGM12WN3GKB" + err = validate.Struct(*e) + suite.EqualError(err, "Key: 'EmailDomainBlock.ID' Error:Field validation for 'ID' failed on the 'ulid' tag") +} + +func (suite *EmailDomainBlockValidateTestSuite) TestValidateEmailDomainBlockNoCreatedAt() { + e := happyEmailDomainBlock() + + e.CreatedAt = time.Time{} + err := validate.Struct(*e) + suite.NoError(err) +} + +func (suite *EmailDomainBlockValidateTestSuite) TestValidateEmailDomainBlockBadDomain() { + e := happyEmailDomainBlock() + + e.Domain = "" + err := validate.Struct(*e) + suite.EqualError(err, "Key: 'EmailDomainBlock.Domain' Error:Field validation for 'Domain' failed on the 'required' tag") + + e.Domain = "this-is-not-a-valid-domain" + err = validate.Struct(*e) + suite.EqualError(err, "Key: 'EmailDomainBlock.Domain' Error:Field validation for 'Domain' failed on the 'fqdn' tag") +} + +func (suite *EmailDomainBlockValidateTestSuite) TestValidateEmailDomainBlockCreatedByAccountID() { + e := happyEmailDomainBlock() + + e.CreatedByAccountID = "" + err := validate.Struct(*e) + suite.EqualError(err, "Key: 'EmailDomainBlock.CreatedByAccountID' Error:Field validation for 'CreatedByAccountID' failed on the 'required' tag") + + e.CreatedByAccountID = "this-is-not-a-valid-ulid" + err = validate.Struct(*e) + suite.EqualError(err, "Key: 'EmailDomainBlock.CreatedByAccountID' Error:Field validation for 'CreatedByAccountID' failed on the 'ulid' tag") +} + +func TestEmailDomainBlockValidateTestSuite(t *testing.T) { + suite.Run(t, new(EmailDomainBlockValidateTestSuite)) +} diff --git a/internal/validate/emoji_test.go b/internal/validate/emoji_test.go new file mode 100644 index 000000000..4ada1ca8b --- /dev/null +++ b/internal/validate/emoji_test.go @@ -0,0 +1,195 @@ +/* + GoToSocial + Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package validate_test + +import ( + "os" + "testing" + "time" + + "github.com/stretchr/testify/suite" + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" + "github.com/superseriousbusiness/gotosocial/internal/validate" +) + +func happyEmoji() *gtsmodel.Emoji { + // the file validator actually runs os.Stat on given paths, so we need to just create small + // temp files for both the main attachment file and the thumbnail + + imageFile, err := os.CreateTemp("", "gts_test_emoji") + if err != nil { + panic(err) + } + if _, err := imageFile.WriteString("main"); err != nil { + panic(err) + } + imagePath := imageFile.Name() + if err := imageFile.Close(); err != nil { + panic(err) + } + + staticFile, err := os.CreateTemp("", "gts_test_emoji_static") + if err != nil { + panic(err) + } + if _, err := staticFile.WriteString("thumbnail"); err != nil { + panic(err) + } + imageStaticPath := staticFile.Name() + if err := staticFile.Close(); err != nil { + panic(err) + } + + return >smodel.Emoji{ + ID: "01F8MH6NEM8D7527KZAECTCR76", + CreatedAt: time.Now().Add(-71 * time.Hour), + UpdatedAt: time.Now().Add(-71 * time.Hour), + Shortcode: "blob_test", + Domain: "example.org", + ImageRemoteURL: "https://example.org/emojis/blob_test.gif", + ImageStaticRemoteURL: "https://example.org/emojis/blob_test.png", + ImageURL: "", + ImageStaticURL: "", + ImagePath: imagePath, + ImageStaticPath: imageStaticPath, + ImageContentType: "image/gif", + ImageStaticContentType: "image/png", + ImageFileSize: 1024, + ImageStaticFileSize: 256, + ImageUpdatedAt: time.Now(), + Disabled: false, + URI: "https://example.org/emojis/blob_test", + VisibleInPicker: true, + 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)) +} diff --git a/internal/validate/follow_test.go b/internal/validate/follow_test.go new file mode 100644 index 000000000..d624666be --- /dev/null +++ b/internal/validate/follow_test.go @@ -0,0 +1,88 @@ +/* + GoToSocial + Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package validate_test + +import ( + "testing" + "time" + + "github.com/stretchr/testify/suite" + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" + "github.com/superseriousbusiness/gotosocial/internal/validate" +) + +func happyFollow() *gtsmodel.Follow { + return >smodel.Follow{ + ID: "01FE91RJR88PSEEE30EV35QR8N", + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + AccountID: "01FE96MAE58MXCE5C4SSMEMCEK", + Account: nil, + TargetAccountID: "01FE96MXRHWZHKC0WH5FT82H1A", + TargetAccount: nil, + URI: "https://example.org/users/user1/activity/follow/01FE91RJR88PSEEE30EV35QR8N", + } +} + +type FollowValidateTestSuite struct { + suite.Suite +} + +func (suite *FollowValidateTestSuite) TestValidateFollowHappyPath() { + // no problem here + f := happyFollow() + err := validate.Struct(*f) + suite.NoError(err) +} + +func (suite *FollowValidateTestSuite) TestValidateFollowBadID() { + f := happyFollow() + + f.ID = "" + err := validate.Struct(*f) + suite.EqualError(err, "Key: 'Follow.ID' Error:Field validation for 'ID' failed on the 'required' tag") + + f.ID = "01FE96W293ZPRG9FQQP48HK8N001FE96W32AT24VYBGM12WN3GKB" + err = validate.Struct(*f) + suite.EqualError(err, "Key: 'Follow.ID' Error:Field validation for 'ID' failed on the 'ulid' tag") +} + +func (suite *FollowValidateTestSuite) TestValidateFollowNoCreatedAt() { + f := happyFollow() + + f.CreatedAt = time.Time{} + err := validate.Struct(*f) + suite.NoError(err) +} + +func (suite *FollowValidateTestSuite) TestValidateFollowNoURI() { + f := happyFollow() + + f.URI = "" + err := validate.Struct(*f) + suite.EqualError(err, "Key: 'Follow.URI' Error:Field validation for 'URI' failed on the 'required' tag") + + f.URI = "this-is-not-a-valid-url" + err = validate.Struct(*f) + suite.EqualError(err, "Key: 'Follow.URI' Error:Field validation for 'URI' failed on the 'url' tag") +} + +func TestFollowValidateTestSuite(t *testing.T) { + suite.Run(t, new(FollowValidateTestSuite)) +} diff --git a/internal/validate/followrequest_test.go b/internal/validate/followrequest_test.go new file mode 100644 index 000000000..f607e24c3 --- /dev/null +++ b/internal/validate/followrequest_test.go @@ -0,0 +1,88 @@ +/* + GoToSocial + Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package validate_test + +import ( + "testing" + "time" + + "github.com/stretchr/testify/suite" + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" + "github.com/superseriousbusiness/gotosocial/internal/validate" +) + +func happyFollowRequest() *gtsmodel.FollowRequest { + return >smodel.FollowRequest{ + ID: "01FE91RJR88PSEEE30EV35QR8N", + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + AccountID: "01FE96MAE58MXCE5C4SSMEMCEK", + Account: nil, + TargetAccountID: "01FE96MXRHWZHKC0WH5FT82H1A", + TargetAccount: nil, + URI: "https://example.org/users/user1/activity/follow/01FE91RJR88PSEEE30EV35QR8N", + } +} + +type FollowRequestValidateTestSuite struct { + suite.Suite +} + +func (suite *FollowRequestValidateTestSuite) TestValidateFollowRequestHappyPath() { + // no problem here + f := happyFollowRequest() + err := validate.Struct(*f) + suite.NoError(err) +} + +func (suite *FollowRequestValidateTestSuite) TestValidateFollowRequestBadID() { + f := happyFollowRequest() + + f.ID = "" + err := validate.Struct(*f) + suite.EqualError(err, "Key: 'FollowRequest.ID' Error:Field validation for 'ID' failed on the 'required' tag") + + f.ID = "01FE96W293ZPRG9FQQP48HK8N001FE96W32AT24VYBGM12WN3GKB" + err = validate.Struct(*f) + suite.EqualError(err, "Key: 'FollowRequest.ID' Error:Field validation for 'ID' failed on the 'ulid' tag") +} + +func (suite *FollowRequestValidateTestSuite) TestValidateFollowRequestNoCreatedAt() { + f := happyFollowRequest() + + f.CreatedAt = time.Time{} + err := validate.Struct(*f) + suite.NoError(err) +} + +func (suite *FollowRequestValidateTestSuite) TestValidateFollowRequestNoURI() { + f := happyFollowRequest() + + f.URI = "" + err := validate.Struct(*f) + suite.EqualError(err, "Key: 'FollowRequest.URI' Error:Field validation for 'URI' failed on the 'required' tag") + + f.URI = "this-is-not-a-valid-url" + err = validate.Struct(*f) + suite.EqualError(err, "Key: 'FollowRequest.URI' Error:Field validation for 'URI' failed on the 'url' tag") +} + +func TestFollowRequestValidateTestSuite(t *testing.T) { + suite.Run(t, new(FollowRequestValidateTestSuite)) +} diff --git a/internal/validate/formvalidation.go b/internal/validate/formvalidation.go new file mode 100644 index 000000000..a30ec1a58 --- /dev/null +++ b/internal/validate/formvalidation.go @@ -0,0 +1,182 @@ +/* + GoToSocial + Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package validate + +import ( + "errors" + "fmt" + "net/mail" + + "github.com/superseriousbusiness/gotosocial/internal/regexes" + pwv "github.com/wagslane/go-password-validator" + "golang.org/x/text/language" +) + +const ( + maximumPasswordLength = 64 + minimumPasswordEntropy = 60 // dictates password strength. See https://github.com/wagslane/go-password-validator + minimumReasonLength = 40 + maximumReasonLength = 500 + maximumSiteTitleLength = 40 + maximumShortDescriptionLength = 500 + maximumDescriptionLength = 5000 + maximumSiteTermsLength = 5000 + maximumUsernameLength = 64 + maximumEmojiShortcodeLength = 30 + maximumHashtagLength = 30 +) + +// NewPassword returns an error if the given password is not sufficiently strong, or nil if it's ok. +func NewPassword(password string) error { + if password == "" { + return errors.New("no password provided") + } + + if len(password) > maximumPasswordLength { + return fmt.Errorf("password should be no more than %d chars", maximumPasswordLength) + } + + return pwv.Validate(password, minimumPasswordEntropy) +} + +// Username makes sure that a given username is valid (ie., letters, numbers, underscores, check length). +// Returns an error if not. +func Username(username string) error { + if username == "" { + return errors.New("no username provided") + } + + if !regexes.Username.MatchString(username) { + return fmt.Errorf("given username %s was invalid: must contain only lowercase letters, numbers, and underscores, max %d characters", username, maximumUsernameLength) + } + + return nil +} + +// Email makes sure that a given email address is a valid address. +// Returns an error if not. +func Email(email string) error { + if email == "" { + return errors.New("no email provided") + } + + _, err := mail.ParseAddress(email) + 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 { + if lang == "" { + return errors.New("no language provided") + } + _, err := language.ParseBase(lang) + return err +} + +// SignUpReason checks that a sufficient reason is given for a server signup request +func SignUpReason(reason string, reasonRequired bool) error { + if !reasonRequired { + // we don't care! + // we're not going to do anything with this text anyway if no reason is required + return nil + } + + if reason == "" { + return errors.New("no reason provided") + } + + if len(reason) < minimumReasonLength { + return fmt.Errorf("reason should be at least %d chars but '%s' was %d", minimumReasonLength, reason, len(reason)) + } + + if len(reason) > maximumReasonLength { + return fmt.Errorf("reason should be no more than %d chars but given reason was %d", maximumReasonLength, len(reason)) + } + return nil +} + +// DisplayName checks that a requested display name is valid +func DisplayName(displayName string) error { + // TODO: add some validation logic here -- length, characters, etc + return nil +} + +// Note checks that a given profile/account note/bio is valid +func Note(note string) error { + // TODO: add some validation logic here -- length, characters, etc + return nil +} + +// Privacy checks that the desired privacy setting is valid +func Privacy(privacy string) error { + // TODO: add some validation logic here -- length, characters, etc + return nil +} + +// EmojiShortcode just runs the given shortcode through the regular expression +// for emoji shortcodes, to figure out whether it's a valid shortcode, ie., 2-30 characters, +// lowercase a-z, numbers, and underscores. +func EmojiShortcode(shortcode string) error { + if !regexes.EmojiShortcode.MatchString(shortcode) { + return fmt.Errorf("shortcode %s did not pass validation, must be between 2 and 30 characters, lowercase letters, numbers, and underscores only", shortcode) + } + return nil +} + +// SiteTitle ensures that the given site title is within spec. +func SiteTitle(siteTitle string) error { + if len(siteTitle) > maximumSiteTitleLength { + return fmt.Errorf("site title should be no more than %d chars but given title was %d", maximumSiteTitleLength, len(siteTitle)) + } + + return nil +} + +// SiteShortDescription ensures that the given site short description is within spec. +func SiteShortDescription(d string) error { + if len(d) > maximumShortDescriptionLength { + return fmt.Errorf("short description should be no more than %d chars but given description was %d", maximumShortDescriptionLength, len(d)) + } + + return nil +} + +// SiteDescription ensures that the given site description is within spec. +func SiteDescription(d string) error { + if len(d) > maximumDescriptionLength { + return fmt.Errorf("description should be no more than %d chars but given description was %d", maximumDescriptionLength, len(d)) + } + + return nil +} + +// SiteTerms ensures that the given site terms string is within spec. +func SiteTerms(t string) error { + if len(t) > maximumSiteTermsLength { + return fmt.Errorf("terms should be no more than %d chars but given terms was %d", maximumSiteTermsLength, len(t)) + } + + return nil +} + +// ULID returns true if the passed string is a valid ULID. +func ULID(i string) bool { + return regexes.ULID.MatchString(i) +} diff --git a/internal/validate/formvalidation_test.go b/internal/validate/formvalidation_test.go new file mode 100644 index 000000000..03bed42d8 --- /dev/null +++ b/internal/validate/formvalidation_test.go @@ -0,0 +1,283 @@ +/* + GoToSocial + Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package validate_test + +import ( + "errors" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" + "github.com/superseriousbusiness/gotosocial/internal/validate" +) + +type ValidationTestSuite struct { + suite.Suite +} + +func (suite *ValidationTestSuite) TestCheckPasswordStrength() { + empty := "" + terriblePassword := "password" + weakPassword := "OKPassword" + shortPassword := "Ok12" + specialPassword := "Ok12%" + longPassword := "thisisafuckinglongpasswordbutnospecialchars" + tooLong := "Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Quisque a enim nibh. Vestibulum bibendum leo ac porttitor auctor." + strongPassword := "3dX5@Zc%mV*W2MBNEy$@" + var err error + + err = validate.NewPassword(empty) + if assert.Error(suite.T(), err) { + assert.Equal(suite.T(), errors.New("no password provided"), err) + } + + err = validate.NewPassword(terriblePassword) + if assert.Error(suite.T(), err) { + assert.Equal(suite.T(), errors.New("insecure password, try including more special characters, using uppercase letters, using numbers or using a longer password"), err) + } + + err = validate.NewPassword(weakPassword) + if assert.Error(suite.T(), err) { + assert.Equal(suite.T(), errors.New("insecure password, try including more special characters, using numbers or using a longer password"), err) + } + + err = validate.NewPassword(shortPassword) + if assert.Error(suite.T(), err) { + assert.Equal(suite.T(), errors.New("insecure password, try including more special characters or using a longer password"), err) + } + + err = validate.NewPassword(specialPassword) + if assert.Error(suite.T(), err) { + assert.Equal(suite.T(), errors.New("insecure password, try including more special characters or using a longer password"), err) + } + + err = validate.NewPassword(longPassword) + if assert.NoError(suite.T(), err) { + assert.Equal(suite.T(), nil, err) + } + + err = validate.NewPassword(tooLong) + if assert.Error(suite.T(), err) { + assert.Equal(suite.T(), errors.New("password should be no more than 64 chars"), err) + } + + err = validate.NewPassword(strongPassword) + if assert.NoError(suite.T(), err) { + assert.Equal(suite.T(), nil, err) + } +} + +func (suite *ValidationTestSuite) TestValidateUsername() { + empty := "" + tooLong := "holycrapthisisthelongestusernameiveeverseeninmylifethatstoomuchman" + withSpaces := "this username has spaces in it" + weirdChars := "thisusername&&&&&&&istooweird!!" + leadingSpace := " see_that_leading_space" + trailingSpace := "thisusername_ends_with_a_space " + newlines := "this_is\n_almost_ok" + goodUsername := "this_is_a_good_username" + var err error + + err = validate.Username(empty) + if assert.Error(suite.T(), err) { + assert.Equal(suite.T(), errors.New("no username provided"), err) + } + + err = validate.Username(tooLong) + if assert.Error(suite.T(), err) { + assert.Equal(suite.T(), fmt.Errorf("given username %s was invalid: must contain only lowercase letters, numbers, and underscores, max 64 characters", tooLong), err) + } + + err = validate.Username(withSpaces) + if assert.Error(suite.T(), err) { + assert.Equal(suite.T(), fmt.Errorf("given username %s was invalid: must contain only lowercase letters, numbers, and underscores, max 64 characters", withSpaces), err) + } + + err = validate.Username(weirdChars) + if assert.Error(suite.T(), err) { + assert.Equal(suite.T(), fmt.Errorf("given username %s was invalid: must contain only lowercase letters, numbers, and underscores, max 64 characters", weirdChars), err) + } + + err = validate.Username(leadingSpace) + if assert.Error(suite.T(), err) { + assert.Equal(suite.T(), fmt.Errorf("given username %s was invalid: must contain only lowercase letters, numbers, and underscores, max 64 characters", leadingSpace), err) + } + + err = validate.Username(trailingSpace) + if assert.Error(suite.T(), err) { + assert.Equal(suite.T(), fmt.Errorf("given username %s was invalid: must contain only lowercase letters, numbers, and underscores, max 64 characters", trailingSpace), err) + } + + err = validate.Username(newlines) + if assert.Error(suite.T(), err) { + assert.Equal(suite.T(), fmt.Errorf("given username %s was invalid: must contain only lowercase letters, numbers, and underscores, max 64 characters", newlines), err) + } + + err = validate.Username(goodUsername) + if assert.NoError(suite.T(), err) { + assert.Equal(suite.T(), nil, err) + } +} + +func (suite *ValidationTestSuite) TestValidateEmail() { + empty := "" + notAnEmailAddress := "this-is-no-email-address!" + almostAnEmailAddress := "@thisisalmostan@email.address" + aWebsite := "https://thisisawebsite.com" + emailAddress := "thisis.actually@anemail.address" + var err error + + err = validate.Email(empty) + if assert.Error(suite.T(), err) { + assert.Equal(suite.T(), errors.New("no email provided"), err) + } + + err = validate.Email(notAnEmailAddress) + if assert.Error(suite.T(), err) { + assert.Equal(suite.T(), errors.New("mail: missing '@' or angle-addr"), err) + } + + err = validate.Email(almostAnEmailAddress) + if assert.Error(suite.T(), err) { + assert.Equal(suite.T(), errors.New("mail: no angle-addr"), err) + } + + err = validate.Email(aWebsite) + if assert.Error(suite.T(), err) { + assert.Equal(suite.T(), errors.New("mail: missing '@' or angle-addr"), err) + } + + err = validate.Email(emailAddress) + if assert.NoError(suite.T(), err) { + assert.Equal(suite.T(), nil, err) + } +} + +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 assert.Error(suite.T(), err) { + assert.Equal(suite.T(), errors.New("no language provided"), err) + } + + err = validate.Language(notALanguage) + if assert.Error(suite.T(), err) { + assert.Equal(suite.T(), errors.New("language: tag is not well-formed"), err) + } + + err = validate.Language(english) + if assert.NoError(suite.T(), err) { + assert.Equal(suite.T(), nil, err) + } + + err = validate.Language(capitalEnglish) + if assert.NoError(suite.T(), err) { + assert.Equal(suite.T(), nil, err) + } + + err = validate.Language(arabic3Letters) + if assert.NoError(suite.T(), err) { + assert.Equal(suite.T(), nil, err) + } + + err = validate.Language(mixedCapsEnglish) + if assert.NoError(suite.T(), err) { + assert.Equal(suite.T(), nil, err) + } + + err = validate.Language(englishUS) + if assert.Error(suite.T(), err) { + assert.Equal(suite.T(), errors.New("language: tag is not well-formed"), err) + } + + err = validate.Language(dutch) + if assert.NoError(suite.T(), err) { + assert.Equal(suite.T(), nil, err) + } + + err = validate.Language(german) + if assert.NoError(suite.T(), err) { + assert.Equal(suite.T(), nil, err) + } +} + +func (suite *ValidationTestSuite) TestValidateReason() { + empty := "" + badReason := "because" + goodReason := "to smash the state and destroy capitalism ultimately and completely" + tooLong := "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris auctor mollis viverra. Maecenas maximus mollis sem, nec fermentum velit consectetur non. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Quisque a enim nibh. Vestibulum bibendum leo ac porttitor auctor. Curabitur velit tellus, facilisis vitae lorem a, ullamcorper efficitur leo. Sed a auctor tortor. Sed ut finibus ante, sit amet laoreet sapien. Donec ullamcorper tellus a nibh sodales vulputate. Donec id dolor eu odio mollis bibendum. Pellentesque habitant morbi tristique senectus et netus at." + var err error + + // check with no reason required + err = validate.SignUpReason(empty, false) + if assert.NoError(suite.T(), err) { + assert.Equal(suite.T(), nil, err) + } + + err = validate.SignUpReason(badReason, false) + if assert.NoError(suite.T(), err) { + assert.Equal(suite.T(), nil, err) + } + + err = validate.SignUpReason(tooLong, false) + if assert.NoError(suite.T(), err) { + assert.Equal(suite.T(), nil, err) + } + + err = validate.SignUpReason(goodReason, false) + if assert.NoError(suite.T(), err) { + assert.Equal(suite.T(), nil, err) + } + + // check with reason required + err = validate.SignUpReason(empty, true) + if assert.Error(suite.T(), err) { + assert.Equal(suite.T(), errors.New("no reason provided"), err) + } + + err = validate.SignUpReason(badReason, true) + if assert.Error(suite.T(), err) { + assert.Equal(suite.T(), errors.New("reason should be at least 40 chars but 'because' was 7"), err) + } + + err = validate.SignUpReason(tooLong, true) + if assert.Error(suite.T(), err) { + assert.Equal(suite.T(), errors.New("reason should be no more than 500 chars but given reason was 600"), err) + } + + err = validate.SignUpReason(goodReason, true) + if assert.NoError(suite.T(), err) { + assert.Equal(suite.T(), nil, err) + } +} + +func TestValidationTestSuite(t *testing.T) { + suite.Run(t, new(ValidationTestSuite)) +} diff --git a/internal/validate/instance_test.go b/internal/validate/instance_test.go new file mode 100644 index 000000000..6042da80e --- /dev/null +++ b/internal/validate/instance_test.go @@ -0,0 +1,146 @@ +/* + GoToSocial + Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package validate_test + +import ( + "testing" + "time" + + "github.com/stretchr/testify/suite" + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" + "github.com/superseriousbusiness/gotosocial/internal/validate" +) + +func happyInstance() *gtsmodel.Instance { + return >smodel.Instance{ + ID: "01FE91RJR88PSEEE30EV35QR8N", + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + Domain: "example.org", + Title: "Example Instance", + URI: "https://example.org", + SuspendedAt: time.Time{}, + DomainBlockID: "", + DomainBlock: nil, + ShortDescription: "This is a description for the example/testing instance.", + Description: "This is a way longer description for the example/testing instance!", + Terms: "Don't be a knobhead.", + ContactEmail: "admin@example.org", + ContactAccountUsername: "admin", + ContactAccountID: "01FEE20H5QWHJDEXAEE9G96PR0", + ContactAccount: nil, + Reputation: 420, + Version: "gotosocial 0.1.0", + } +} + +type InstanceValidateTestSuite struct { + suite.Suite +} + +func (suite *InstanceValidateTestSuite) TestValidateInstanceHappyPath() { + // no problem here + m := happyInstance() + err := validate.Struct(*m) + suite.NoError(err) +} + +func (suite *InstanceValidateTestSuite) TestValidateInstanceBadID() { + m := happyInstance() + + m.ID = "" + err := validate.Struct(*m) + suite.EqualError(err, "Key: 'Instance.ID' Error:Field validation for 'ID' failed on the 'required' tag") + + m.ID = "01FE96W293ZPRG9FQQP48HK8N001FE96W32AT24VYBGM12WN3GKB" + err = validate.Struct(*m) + suite.EqualError(err, "Key: 'Instance.ID' Error:Field validation for 'ID' failed on the 'ulid' tag") +} + +func (suite *InstanceValidateTestSuite) TestValidateInstanceAccountURI() { + i := happyInstance() + + i.URI = "" + err := validate.Struct(*i) + suite.EqualError(err, "Key: 'Instance.URI' Error:Field validation for 'URI' failed on the 'required' tag") + + i.URI = "---------------------------" + err = validate.Struct(*i) + suite.EqualError(err, "Key: 'Instance.URI' Error:Field validation for 'URI' failed on the 'url' tag") +} + +func (suite *InstanceValidateTestSuite) TestValidateInstanceDodgyAccountID() { + i := happyInstance() + + i.ContactAccountID = "9HZJ76B6VXSKF" + err := validate.Struct(*i) + suite.EqualError(err, "Key: 'Instance.ContactAccountID' Error:Field validation for 'ContactAccountID' failed on the 'ulid' tag") + + i.ContactAccountID = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!!!!!!!!!!!!" + err = validate.Struct(*i) + suite.EqualError(err, "Key: 'Instance.ContactAccountID' Error:Field validation for 'ContactAccountID' failed on the 'ulid' tag") + + i.ContactAccountID = "" + err = validate.Struct(*i) + suite.EqualError(err, "Key: 'Instance.ContactAccountID' Error:Field validation for 'ContactAccountID' failed on the 'required_with' tag") + + i.ContactAccountUsername = "" + err = validate.Struct(*i) + suite.NoError(err) +} + +func (suite *InstanceValidateTestSuite) TestValidateInstanceDomain() { + i := happyInstance() + + i.Domain = "poopoo" + err := validate.Struct(*i) + suite.EqualError(err, "Key: 'Instance.Domain' Error:Field validation for 'Domain' failed on the 'fqdn' tag") + + i.Domain = "" + err = validate.Struct(*i) + suite.EqualError(err, "Key: 'Instance.Domain' Error:Field validation for 'Domain' failed on the 'required' tag") + + i.Domain = "https://aaaaaaaaaaaaah.org" + err = validate.Struct(*i) + suite.EqualError(err, "Key: 'Instance.Domain' Error:Field validation for 'Domain' failed on the 'fqdn' tag") +} + +func (suite *InstanceValidateTestSuite) TestValidateInstanceContactEmail() { + i := happyInstance() + + i.ContactEmail = "poopoo" + err := validate.Struct(*i) + suite.EqualError(err, "Key: 'Instance.ContactEmail' Error:Field validation for 'ContactEmail' failed on the 'email' tag") + + i.ContactEmail = "" + err = validate.Struct(*i) + suite.NoError(err) +} + +func (suite *InstanceValidateTestSuite) TestValidateInstanceNoCreatedAt() { + i := happyInstance() + + i.CreatedAt = time.Time{} + err := validate.Struct(*i) + suite.NoError(err) +} + +func TestInstanceValidateTestSuite(t *testing.T) { + suite.Run(t, new(InstanceValidateTestSuite)) +} diff --git a/internal/validate/mediaattachment_test.go b/internal/validate/mediaattachment_test.go new file mode 100644 index 000000000..84c2fa4eb --- /dev/null +++ b/internal/validate/mediaattachment_test.go @@ -0,0 +1,230 @@ +/* + GoToSocial + Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package validate_test + +import ( + "os" + "testing" + "time" + + "github.com/stretchr/testify/suite" + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" + "github.com/superseriousbusiness/gotosocial/internal/validate" +) + +func happyMediaAttachment() *gtsmodel.MediaAttachment { + // the file validator actually runs os.Stat on given paths, so we need to just create small + // temp files for both the main attachment file and the thumbnail + + mainFile, err := os.CreateTemp("", "gts_test_mainfile") + if err != nil { + panic(err) + } + if _, err := mainFile.WriteString("main"); err != nil { + panic(err) + } + mainPath := mainFile.Name() + if err := mainFile.Close(); err != nil { + panic(err) + } + + thumbnailFile, err := os.CreateTemp("", "gts_test_thumbnail") + if err != nil { + panic(err) + } + if _, err := thumbnailFile.WriteString("thumbnail"); err != nil { + panic(err) + } + thumbnailPath := thumbnailFile.Name() + if err := thumbnailFile.Close(); err != nil { + panic(err) + } + + return >smodel.MediaAttachment{ + ID: "01F8MH6NEM8D7527KZAECTCR76", + CreatedAt: time.Now().Add(-71 * time.Hour), + UpdatedAt: time.Now().Add(-71 * time.Hour), + StatusID: "01F8MH75CBF9JFX4ZAD54N0W0R", + URL: "http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/attachment/original/01F8MH6NEM8D7527KZAECTCR76.jpeg", + 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.jpeg", + RemoteURL: "", + }, + Avatar: false, + Header: false, + } +} + +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)) +} diff --git a/internal/validate/mention_test.go b/internal/validate/mention_test.go new file mode 100644 index 000000000..59ba1e3fd --- /dev/null +++ b/internal/validate/mention_test.go @@ -0,0 +1,102 @@ +/* + GoToSocial + Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package validate_test + +import ( + "testing" + "time" + + "github.com/stretchr/testify/suite" + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" + "github.com/superseriousbusiness/gotosocial/internal/validate" +) + +func happyMention() *gtsmodel.Mention { + return >smodel.Mention{ + ID: "01FE91RJR88PSEEE30EV35QR8N", + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + OriginAccountID: "01FE96MAE58MXCE5C4SSMEMCEK", + OriginAccountURI: "https://some-instance/accounts/bleepbloop", + OriginAccount: nil, + TargetAccountID: "01FE96MXRHWZHKC0WH5FT82H1A", + TargetAccount: nil, + StatusID: "01FE96NBPNJNY26730FT6GZTFE", + Status: nil, + } +} + +type MentionValidateTestSuite struct { + suite.Suite +} + +func (suite *MentionValidateTestSuite) TestValidateMentionHappyPath() { + // no problem here + m := happyMention() + err := validate.Struct(*m) + suite.NoError(err) +} + +func (suite *MentionValidateTestSuite) TestValidateMentionBadID() { + m := happyMention() + + m.ID = "" + err := validate.Struct(*m) + suite.EqualError(err, "Key: 'Mention.ID' Error:Field validation for 'ID' failed on the 'required' tag") + + m.ID = "01FE96W293ZPRG9FQQP48HK8N001FE96W32AT24VYBGM12WN3GKB" + err = validate.Struct(*m) + suite.EqualError(err, "Key: 'Mention.ID' Error:Field validation for 'ID' failed on the 'ulid' tag") +} + +func (suite *MentionValidateTestSuite) TestValidateMentionAccountURI() { + m := happyMention() + + m.OriginAccountURI = "" + err := validate.Struct(*m) + suite.EqualError(err, "Key: 'Mention.OriginAccountURI' Error:Field validation for 'OriginAccountURI' failed on the 'url' tag") + + m.OriginAccountURI = "---------------------------" + err = validate.Struct(*m) + suite.EqualError(err, "Key: 'Mention.OriginAccountURI' Error:Field validation for 'OriginAccountURI' failed on the 'url' tag") +} + +func (suite *MentionValidateTestSuite) TestValidateMentionDodgyStatusID() { + m := happyMention() + + m.StatusID = "9HZJ76B6VXSKF" + err := validate.Struct(*m) + suite.EqualError(err, "Key: 'Mention.StatusID' Error:Field validation for 'StatusID' failed on the 'ulid' tag") + + m.StatusID = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!!!!!!!!!!!!" + err = validate.Struct(*m) + suite.EqualError(err, "Key: 'Mention.StatusID' Error:Field validation for 'StatusID' failed on the 'ulid' tag") +} + +func (suite *MentionValidateTestSuite) TestValidateMentionNoCreatedAt() { + m := happyMention() + + m.CreatedAt = time.Time{} + err := validate.Struct(*m) + suite.NoError(err) +} + +func TestMentionValidateTestSuite(t *testing.T) { + suite.Run(t, new(MentionValidateTestSuite)) +} diff --git a/internal/validate/notification_test.go b/internal/validate/notification_test.go new file mode 100644 index 000000000..700560f2a --- /dev/null +++ b/internal/validate/notification_test.go @@ -0,0 +1,98 @@ +/* + GoToSocial + Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package validate_test + +import ( + "testing" + "time" + + "github.com/stretchr/testify/suite" + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" + "github.com/superseriousbusiness/gotosocial/internal/validate" +) + +func happyNotification() *gtsmodel.Notification { + return >smodel.Notification{ + ID: "01FE91RJR88PSEEE30EV35QR8N", + CreatedAt: time.Now(), + NotificationType: gtsmodel.NotificationFave, + OriginAccountID: "01FE96MAE58MXCE5C4SSMEMCEK", + OriginAccount: nil, + TargetAccountID: "01FE96MXRHWZHKC0WH5FT82H1A", + TargetAccount: nil, + StatusID: "01FE96NBPNJNY26730FT6GZTFE", + Status: nil, + } +} + +type NotificationValidateTestSuite struct { + suite.Suite +} + +func (suite *NotificationValidateTestSuite) TestValidateNotificationHappyPath() { + // no problem here + m := happyNotification() + err := validate.Struct(*m) + suite.NoError(err) +} + +func (suite *NotificationValidateTestSuite) TestValidateNotificationBadID() { + m := happyNotification() + + m.ID = "" + err := validate.Struct(*m) + suite.EqualError(err, "Key: 'Notification.ID' Error:Field validation for 'ID' failed on the 'required' tag") + + m.ID = "01FE96W293ZPRG9FQQP48HK8N001FE96W32AT24VYBGM12WN3GKB" + err = validate.Struct(*m) + suite.EqualError(err, "Key: 'Notification.ID' Error:Field validation for 'ID' failed on the 'ulid' tag") +} + +func (suite *NotificationValidateTestSuite) TestValidateNotificationStatusID() { + m := happyNotification() + + m.StatusID = "" + err := validate.Struct(*m) + suite.EqualError(err, "Key: 'Notification.StatusID' Error:Field validation for 'StatusID' failed on the 'required_if' tag") + + m.StatusID = "9HZJ76B6VXSKF" + err = validate.Struct(*m) + suite.EqualError(err, "Key: 'Notification.StatusID' Error:Field validation for 'StatusID' failed on the 'ulid' tag") + + m.StatusID = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!!!!!!!!!!!!" + err = validate.Struct(*m) + suite.EqualError(err, "Key: 'Notification.StatusID' Error:Field validation for 'StatusID' failed on the 'ulid' tag") + + m.StatusID = "" + m.NotificationType = gtsmodel.NotificationFollowRequest + err = validate.Struct(*m) + suite.NoError(err) +} + +func (suite *NotificationValidateTestSuite) TestValidateNotificationNoCreatedAt() { + m := happyNotification() + + m.CreatedAt = time.Time{} + err := validate.Struct(*m) + suite.NoError(err) +} + +func TestNotificationValidateTestSuite(t *testing.T) { + suite.Run(t, new(NotificationValidateTestSuite)) +} diff --git a/internal/validate/routersession_test.go b/internal/validate/routersession_test.go new file mode 100644 index 000000000..7ab4034d1 --- /dev/null +++ b/internal/validate/routersession_test.go @@ -0,0 +1,88 @@ +/* + GoToSocial + Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package validate_test + +import ( + "testing" + + "github.com/stretchr/testify/suite" + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" + "github.com/superseriousbusiness/gotosocial/internal/validate" +) + +func happyRouterSession() *gtsmodel.RouterSession { + return >smodel.RouterSession{ + ID: "01FE91RJR88PSEEE30EV35QR8N", + Auth: []byte("12345678901234567890123456789012"), + Crypt: []byte("12345678901234567890123456789012"), + } +} + +type RouterSessionValidateTestSuite struct { + suite.Suite +} + +func (suite *RouterSessionValidateTestSuite) TestValidateRouterSessionHappyPath() { + // no problem here + r := happyRouterSession() + err := validate.Struct(*r) + suite.NoError(err) +} + +func (suite *RouterSessionValidateTestSuite) TestValidateRouterSessionAuth() { + r := happyRouterSession() + + // remove auth struct + r.Auth = nil + err := validate.Struct(*r) + suite.EqualError(err, "Key: 'RouterSession.Auth' Error:Field validation for 'Auth' failed on the 'required' tag") + + // auth bytes too long + r.Auth = []byte("1234567890123456789012345678901234567890") + err = validate.Struct(*r) + suite.EqualError(err, "Key: 'RouterSession.Auth' Error:Field validation for 'Auth' failed on the 'len' tag") + + // auth bytes too short + r.Auth = []byte("12345678901") + err = validate.Struct(*r) + suite.EqualError(err, "Key: 'RouterSession.Auth' Error:Field validation for 'Auth' failed on the 'len' tag") +} + +func (suite *RouterSessionValidateTestSuite) TestValidateRouterSessionCrypt() { + r := happyRouterSession() + + // remove crypt struct + r.Crypt = nil + err := validate.Struct(*r) + suite.EqualError(err, "Key: 'RouterSession.Crypt' Error:Field validation for 'Crypt' failed on the 'required' tag") + + // crypt bytes too long + r.Crypt = []byte("1234567890123456789012345678901234567890") + err = validate.Struct(*r) + suite.EqualError(err, "Key: 'RouterSession.Crypt' Error:Field validation for 'Crypt' failed on the 'len' tag") + + // crypt bytes too short + r.Crypt = []byte("12345678901") + err = validate.Struct(*r) + suite.EqualError(err, "Key: 'RouterSession.Crypt' Error:Field validation for 'Crypt' failed on the 'len' tag") +} + +func TestRouterSessionValidateTestSuite(t *testing.T) { + suite.Run(t, new(RouterSessionValidateTestSuite)) +} diff --git a/internal/validate/status_test.go b/internal/validate/status_test.go new file mode 100644 index 000000000..57e7259b9 --- /dev/null +++ b/internal/validate/status_test.go @@ -0,0 +1,163 @@ +/* + GoToSocial + Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +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" +) + +func happyStatus() *gtsmodel.Status { + return >smodel.Status{ + ID: "01FEBBH6NYDG87NK6A6EC543ED", + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + URI: "https://example.org/users/test_user/statuses/01FEBBH6NYDG87NK6A6EC543ED", + URL: "https://example.org/@test_user/01FEBBH6NYDG87NK6A6EC543ED", + Content: "

Test status! #hello

", + AttachmentIDs: []string{"01FEBBKZBY9H5FEP3PHVVAAGN1", "01FEBBM7S2R4WT6WWW22KN1PWE"}, + Attachments: nil, + TagIDs: []string{"01FEBBNBMBSN1FESMZ1TCXNWYP"}, + Tags: nil, + MentionIDs: nil, + Mentions: nil, + EmojiIDs: nil, + Emojis: nil, + Local: true, + 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: false, + Language: "en", + CreatedWithApplicationID: "01FEBBZHF4GFVRXSJVXD0JTZZ2", + CreatedWithApplication: nil, + VisibilityAdvanced: gtsmodel.VisibilityAdvanced{ + Federated: true, + Boostable: true, + Replyable: true, + Likeable: true, + }, + ActivityStreamsType: ap.ObjectNote, + Text: "Test status! #hello", + Pinned: false, + } +} + +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 = false + 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)) +} diff --git a/internal/validate/statusbookmark_test.go b/internal/validate/statusbookmark_test.go new file mode 100644 index 000000000..667994aa0 --- /dev/null +++ b/internal/validate/statusbookmark_test.go @@ -0,0 +1,88 @@ +/* + GoToSocial + Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package validate_test + +import ( + "testing" + "time" + + "github.com/stretchr/testify/suite" + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" + "github.com/superseriousbusiness/gotosocial/internal/validate" +) + +func happyStatusBookmark() *gtsmodel.StatusBookmark { + return >smodel.StatusBookmark{ + ID: "01FE91RJR88PSEEE30EV35QR8N", + CreatedAt: time.Now(), + AccountID: "01FE96MAE58MXCE5C4SSMEMCEK", + Account: nil, + TargetAccountID: "01FE96MXRHWZHKC0WH5FT82H1A", + TargetAccount: nil, + StatusID: "01FE96NBPNJNY26730FT6GZTFE", + Status: nil, + } +} + +type StatusBookmarkValidateTestSuite struct { + suite.Suite +} + +func (suite *StatusBookmarkValidateTestSuite) TestValidateStatusBookmarkHappyPath() { + // no problem here + m := happyStatusBookmark() + err := validate.Struct(*m) + suite.NoError(err) +} + +func (suite *StatusBookmarkValidateTestSuite) TestValidateStatusBookmarkBadID() { + m := happyStatusBookmark() + + m.ID = "" + err := validate.Struct(*m) + suite.EqualError(err, "Key: 'StatusBookmark.ID' Error:Field validation for 'ID' failed on the 'required' tag") + + m.ID = "01FE96W293ZPRG9FQQP48HK8N001FE96W32AT24VYBGM12WN3GKB" + err = validate.Struct(*m) + suite.EqualError(err, "Key: 'StatusBookmark.ID' Error:Field validation for 'ID' failed on the 'ulid' tag") +} + +func (suite *StatusBookmarkValidateTestSuite) TestValidateStatusBookmarkDodgyStatusID() { + m := happyStatusBookmark() + + m.StatusID = "9HZJ76B6VXSKF" + err := validate.Struct(*m) + suite.EqualError(err, "Key: 'StatusBookmark.StatusID' Error:Field validation for 'StatusID' failed on the 'ulid' tag") + + m.StatusID = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!!!!!!!!!!!!" + err = validate.Struct(*m) + suite.EqualError(err, "Key: 'StatusBookmark.StatusID' Error:Field validation for 'StatusID' failed on the 'ulid' tag") +} + +func (suite *StatusBookmarkValidateTestSuite) TestValidateStatusBookmarkNoCreatedAt() { + m := happyStatusBookmark() + + m.CreatedAt = time.Time{} + err := validate.Struct(*m) + suite.NoError(err) +} + +func TestStatusBookmarkValidateTestSuite(t *testing.T) { + suite.Run(t, new(StatusBookmarkValidateTestSuite)) +} diff --git a/internal/validate/statusfave_test.go b/internal/validate/statusfave_test.go new file mode 100644 index 000000000..52bef1749 --- /dev/null +++ b/internal/validate/statusfave_test.go @@ -0,0 +1,101 @@ +/* + GoToSocial + Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package validate_test + +import ( + "testing" + "time" + + "github.com/stretchr/testify/suite" + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" + "github.com/superseriousbusiness/gotosocial/internal/validate" +) + +func happyStatusFave() *gtsmodel.StatusFave { + return >smodel.StatusFave{ + ID: "01FE91RJR88PSEEE30EV35QR8N", + CreatedAt: time.Now(), + AccountID: "01FE96MAE58MXCE5C4SSMEMCEK", + Account: nil, + TargetAccountID: "01FE96MXRHWZHKC0WH5FT82H1A", + TargetAccount: nil, + StatusID: "01FE96NBPNJNY26730FT6GZTFE", + Status: nil, + URI: "https://example.org/users/user1/activity/faves/01FE91RJR88PSEEE30EV35QR8N", + } +} + +type StatusFaveValidateTestSuite struct { + suite.Suite +} + +func (suite *StatusFaveValidateTestSuite) TestValidateStatusFaveHappyPath() { + // no problem here + f := happyStatusFave() + err := validate.Struct(*f) + suite.NoError(err) +} + +func (suite *StatusFaveValidateTestSuite) TestValidateStatusFaveBadID() { + f := happyStatusFave() + + f.ID = "" + err := validate.Struct(*f) + suite.EqualError(err, "Key: 'StatusFave.ID' Error:Field validation for 'ID' failed on the 'required' tag") + + f.ID = "01FE96W293ZPRG9FQQP48HK8N001FE96W32AT24VYBGM12WN3GKB" + err = validate.Struct(*f) + suite.EqualError(err, "Key: 'StatusFave.ID' Error:Field validation for 'ID' failed on the 'ulid' tag") +} + +func (suite *StatusFaveValidateTestSuite) TestValidateStatusFaveDodgyStatusID() { + f := happyStatusFave() + + f.StatusID = "9HZJ76B6VXSKF" + err := validate.Struct(*f) + suite.EqualError(err, "Key: 'StatusFave.StatusID' Error:Field validation for 'StatusID' failed on the 'ulid' tag") + + f.StatusID = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!!!!!!!!!!!!" + err = validate.Struct(*f) + suite.EqualError(err, "Key: 'StatusFave.StatusID' Error:Field validation for 'StatusID' failed on the 'ulid' tag") +} + +func (suite *StatusFaveValidateTestSuite) TestValidateStatusFaveNoCreatedAt() { + f := happyStatusFave() + + f.CreatedAt = time.Time{} + err := validate.Struct(*f) + suite.NoError(err) +} + +func (suite *StatusFaveValidateTestSuite) TestValidateStatusFaveNoURI() { + f := happyStatusFave() + + f.URI = "" + err := validate.Struct(*f) + suite.EqualError(err, "Key: 'StatusFave.URI' Error:Field validation for 'URI' failed on the 'required' tag") + + f.URI = "this-is-not-a-valid-url" + err = validate.Struct(*f) + suite.EqualError(err, "Key: 'StatusFave.URI' Error:Field validation for 'URI' failed on the 'url' tag") +} + +func TestStatusFaveValidateTestSuite(t *testing.T) { + suite.Run(t, new(StatusFaveValidateTestSuite)) +} diff --git a/internal/validate/statusmute_test.go b/internal/validate/statusmute_test.go new file mode 100644 index 000000000..de23114eb --- /dev/null +++ b/internal/validate/statusmute_test.go @@ -0,0 +1,88 @@ +/* + GoToSocial + Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package validate_test + +import ( + "testing" + "time" + + "github.com/stretchr/testify/suite" + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" + "github.com/superseriousbusiness/gotosocial/internal/validate" +) + +func happyStatusMute() *gtsmodel.StatusMute { + return >smodel.StatusMute{ + ID: "01FE91RJR88PSEEE30EV35QR8N", + CreatedAt: time.Now(), + AccountID: "01FE96MAE58MXCE5C4SSMEMCEK", + Account: nil, + TargetAccountID: "01FE96MXRHWZHKC0WH5FT82H1A", + TargetAccount: nil, + StatusID: "01FE96NBPNJNY26730FT6GZTFE", + Status: nil, + } +} + +type StatusMuteValidateTestSuite struct { + suite.Suite +} + +func (suite *StatusMuteValidateTestSuite) TestValidateStatusMuteHappyPath() { + // no problem here + m := happyStatusMute() + err := validate.Struct(*m) + suite.NoError(err) +} + +func (suite *StatusMuteValidateTestSuite) TestValidateStatusMuteBadID() { + m := happyStatusMute() + + m.ID = "" + err := validate.Struct(*m) + suite.EqualError(err, "Key: 'StatusMute.ID' Error:Field validation for 'ID' failed on the 'required' tag") + + m.ID = "01FE96W293ZPRG9FQQP48HK8N001FE96W32AT24VYBGM12WN3GKB" + err = validate.Struct(*m) + suite.EqualError(err, "Key: 'StatusMute.ID' Error:Field validation for 'ID' failed on the 'ulid' tag") +} + +func (suite *StatusMuteValidateTestSuite) TestValidateStatusMuteDodgyStatusID() { + m := happyStatusMute() + + m.StatusID = "9HZJ76B6VXSKF" + err := validate.Struct(*m) + suite.EqualError(err, "Key: 'StatusMute.StatusID' Error:Field validation for 'StatusID' failed on the 'ulid' tag") + + m.StatusID = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!!!!!!!!!!!!" + err = validate.Struct(*m) + suite.EqualError(err, "Key: 'StatusMute.StatusID' Error:Field validation for 'StatusID' failed on the 'ulid' tag") +} + +func (suite *StatusMuteValidateTestSuite) TestValidateStatusMuteNoCreatedAt() { + m := happyStatusMute() + + m.CreatedAt = time.Time{} + err := validate.Struct(*m) + suite.NoError(err) +} + +func TestStatusMuteValidateTestSuite(t *testing.T) { + suite.Run(t, new(StatusMuteValidateTestSuite)) +} diff --git a/internal/validate/structvalidation.go b/internal/validate/structvalidation.go new file mode 100644 index 000000000..7717822d9 --- /dev/null +++ b/internal/validate/structvalidation.go @@ -0,0 +1,75 @@ +/* + GoToSocial + Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package validate + +import ( + "reflect" + + "github.com/go-playground/validator/v10" + "github.com/superseriousbusiness/gotosocial/internal/regexes" +) + +var v *validator.Validate + +// Validation Panic messages +const ( + PointerPanic = "validate function was passed pointer" + InvalidPanic = "validate function was passed invalid item" +) + +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() + v.RegisterValidation("ulid", ulidValidator) +} + +// Struct validates the passed struct, returning validator.ValidationErrors if invalid, or nil if OK. +func Struct(s interface{}) error { + switch reflect.ValueOf(s).Kind() { + case reflect.Invalid: + panic(InvalidPanic) + case reflect.Ptr: + panic(PointerPanic) + } + + err := v.Struct(s) + return processValidationError(err) +} + +func processValidationError(err error) error { + if err == nil { + return nil + } + + if ive, ok := err.(*validator.InvalidValidationError); ok { + panic(ive) + } + + return err.(validator.ValidationErrors) +} diff --git a/internal/validate/structvalidation_test.go b/internal/validate/structvalidation_test.go new file mode 100644 index 000000000..606d92da4 --- /dev/null +++ b/internal/validate/structvalidation_test.go @@ -0,0 +1,65 @@ +/* + GoToSocial + Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +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) TestValidatePointer() { + var nilUser *gtsmodel.User + suite.PanicsWithValue(validate.PointerPanic, func() { + validate.Struct(nilUser) + }) +} + +func (suite *ValidateTestSuite) TestValidateNil() { + suite.PanicsWithValue(validate.InvalidPanic, 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)) +} diff --git a/internal/validate/tag_test.go b/internal/validate/tag_test.go new file mode 100644 index 000000000..4f8ab2fba --- /dev/null +++ b/internal/validate/tag_test.go @@ -0,0 +1,93 @@ +/* + GoToSocial + Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package validate_test + +import ( + "testing" + "time" + + "github.com/stretchr/testify/suite" + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" + "github.com/superseriousbusiness/gotosocial/internal/validate" +) + +func happyTag() *gtsmodel.Tag { + return >smodel.Tag{ + ID: "01FE91RJR88PSEEE30EV35QR8N", + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + URL: "https://example.org/tags/some_tag", + Name: "some_tag", + FirstSeenFromAccountID: "01FE91SR5P2GW06K3AJ98P72MT", + Useable: true, + Listable: true, + LastStatusAt: time.Now(), + } +} + +type TagValidateTestSuite struct { + suite.Suite +} + +func (suite *TagValidateTestSuite) TestValidateTagHappyPath() { + // no problem here + t := happyTag() + err := validate.Struct(*t) + suite.NoError(err) +} + +func (suite *TagValidateTestSuite) TestValidateTagNoName() { + t := happyTag() + t.Name = "" + + err := validate.Struct(*t) + suite.EqualError(err, "Key: 'Tag.Name' Error:Field validation for 'Name' failed on the 'required' tag") +} + +func (suite *TagValidateTestSuite) TestValidateTagBadURL() { + t := happyTag() + + t.URL = "" + err := validate.Struct(*t) + suite.EqualError(err, "Key: 'Tag.URL' Error:Field validation for 'URL' failed on the 'required' tag") + + t.URL = "no-schema.com" + err = validate.Struct(*t) + suite.EqualError(err, "Key: 'Tag.URL' Error:Field validation for 'URL' failed on the 'url' tag") + + t.URL = "justastring" + err = validate.Struct(*t) + suite.EqualError(err, "Key: 'Tag.URL' Error:Field validation for 'URL' failed on the 'url' tag") + + t.URL = "https://aaa\n\n\naaaaaaaa" + err = validate.Struct(*t) + suite.EqualError(err, "Key: 'Tag.URL' Error:Field validation for 'URL' failed on the 'url' tag") +} + +func (suite *TagValidateTestSuite) TestValidateTagNoFirstSeenFromAccountID() { + t := happyTag() + t.FirstSeenFromAccountID = "" + + err := validate.Struct(*t) + suite.NoError(err) +} + +func TestTagValidateTestSuite(t *testing.T) { + suite.Run(t, new(TagValidateTestSuite)) +} diff --git a/internal/validate/user_test.go b/internal/validate/user_test.go new file mode 100644 index 000000000..5a13a510f --- /dev/null +++ b/internal/validate/user_test.go @@ -0,0 +1,125 @@ +/* + GoToSocial + Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package validate_test + +import ( + "net" + "testing" + "time" + + "github.com/stretchr/testify/suite" + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" + "github.com/superseriousbusiness/gotosocial/internal/validate" +) + +func happyUser() *gtsmodel.User { + return >smodel.User{ + ID: "01FE8TTK9F34BR0KG7639AJQTX", + Email: "whatever@example.org", + AccountID: "01FE8TWA7CN8J7237K5DFS1RY5", + Account: nil, + EncryptedPassword: "$2y$10$tkRapNGW.RWkEuCMWdgArunABFvsPGRvFQY3OibfSJo0RDL3z8WfC", + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + SignUpIP: net.ParseIP("128.64.32.16"), + CurrentSignInAt: time.Now(), + CurrentSignInIP: net.ParseIP("128.64.32.16"), + LastSignInAt: time.Now(), + LastSignInIP: net.ParseIP("128.64.32.16"), + SignInCount: 0, + InviteID: "", + ChosenLanguages: []string{}, + FilteredLanguages: []string{}, + Locale: "en", + CreatedByApplicationID: "01FE8Y5EHMWCA1MHMTNHRVZ1X4", + CreatedByApplication: nil, + LastEmailedAt: time.Now(), + ConfirmationToken: "", + ConfirmedAt: time.Now(), + ConfirmationSentAt: time.Time{}, + UnconfirmedEmail: "", + Moderator: false, + Admin: false, + Disabled: false, + Approved: true, + } +} + +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 TestUserValidateTestSuite(t *testing.T) { + suite.Run(t, new(UserValidateTestSuite)) +} diff --git a/testrig/testmodels.go b/testrig/testmodels.go index 388c0cadd..a38e72329 100644 --- a/testrig/testmodels.go +++ b/testrig/testmodels.go @@ -103,7 +103,6 @@ func NewTestApplications() map[string]*gtsmodel.Application { ClientID: "01F8MGWSJCND9BWBD4WGJXBM93", // admin client ClientSecret: "dda8e835-2c9c-4bd2-9b8b-77c2e26d7a7a", // admin client Scopes: "read write follow push", - VapidKey: "76ae0095-8a10-438f-9f49-522d1985b190", }, "application_1": { ID: "01F8MGY43H3N2C8EWPR2FPYEXG", @@ -113,7 +112,6 @@ func NewTestApplications() map[string]*gtsmodel.Application { ClientID: "01F8MGV8AC3NGSJW0FE8W1BV70", // client_1 ClientSecret: "c3724c74-dc3b-41b2-a108-0ea3d8399830", // client_1 Scopes: "read write follow push", - VapidKey: "4738dfd7-ca73-4aa6-9aa9-80e946b7db36", }, "application_2": { ID: "01F8MGYG9E893WRHW0TAEXR8GJ", @@ -123,7 +121,6 @@ func NewTestApplications() map[string]*gtsmodel.Application { ClientID: "01F8MGW47HN8ZXNHNZ7E47CDMQ", // client_2 ClientSecret: "8f5603a5-c721-46cd-8f1b-2e368f51379f", // client_2 Scopes: "read write follow push", - VapidKey: "c040a5fc-e1e2-4859-bbea-0a3efbca1c4b", }, } return apps -- cgit v1.2.3