diff options
Diffstat (limited to 'internal/gtsmodel')
| -rw-r--r-- | internal/gtsmodel/routersession.go | 6 | ||||
| -rw-r--r-- | internal/gtsmodel/status.go | 130 | ||||
| -rw-r--r-- | internal/gtsmodel/status_test.go | 161 | ||||
| -rw-r--r-- | internal/gtsmodel/statusbookmark.go | 22 | ||||
| -rw-r--r-- | internal/gtsmodel/statusbookmark_test.go | 87 | ||||
| -rw-r--r-- | internal/gtsmodel/statusfave.go | 24 | ||||
| -rw-r--r-- | internal/gtsmodel/statusfave_test.go | 100 | ||||
| -rw-r--r-- | internal/gtsmodel/user.go | 2 | ||||
| -rw-r--r-- | internal/gtsmodel/user_test.go | 2 | 
9 files changed, 420 insertions, 114 deletions
diff --git a/internal/gtsmodel/routersession.go b/internal/gtsmodel/routersession.go index fbc1c7768..374264fe4 100644 --- a/internal/gtsmodel/routersession.go +++ b/internal/gtsmodel/routersession.go @@ -20,7 +20,7 @@ package gtsmodel  // RouterSession is used to store and retrieve settings for a router session.  type RouterSession struct { -	ID    string `bun:"type:CHAR(26),pk,notnull"` -	Auth  []byte `bun:"type:bytea,notnull,nullzero"` -	Crypt []byte `bun:"type:bytea,notnull,nullzero"` +	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/gtsmodel/status.go b/internal/gtsmodel/status.go index 1997ad5df..7c224f255 100644 --- a/internal/gtsmodel/status.go +++ b/internal/gtsmodel/status.go @@ -24,87 +24,59 @@ import (  // Status represents a user-created 'post' or 'status' in the database, either remote or local  type Status struct { -	// id of the status in the database -	ID string `bun:"type:CHAR(26),pk,notnull"` -	// uri at which this status is reachable -	URI string `bun:",unique,nullzero"` -	// web url for viewing this status -	URL string `bun:",unique,nullzero"` -	// the html-formatted content of this status -	Content string `bun:",nullzero"` -	// Database IDs of any media attachments associated with this status -	AttachmentIDs []string           `bun:"attachments,array"` -	Attachments   []*MediaAttachment `bun:"attached_media,rel:has-many"` -	// Database IDs of any tags used in this status -	TagIDs []string `bun:"tags,array"` -	Tags   []*Tag   `bun:"attached_tags,m2m:status_to_tags"` // https://bun.uptrace.dev/guide/relations.html#many-to-many-relation -	// Database IDs of any mentions in this status -	MentionIDs []string   `bun:"mentions,array"` -	Mentions   []*Mention `bun:"attached_mentions,rel:has-many"` -	// Database IDs of any emojis used in this status -	EmojiIDs []string `bun:"emojis,array"` -	Emojis   []*Emoji `bun:"attached_emojis,m2m:status_to_emojis"` // https://bun.uptrace.dev/guide/relations.html#many-to-many-relation -	// when was this status created? -	CreatedAt time.Time `bun:",notnull,nullzero,default:current_timestamp"` -	// when was this status updated? -	UpdatedAt time.Time `bun:",notnull,nullzero,default:current_timestamp"` -	// is this status from a local account? -	Local bool -	// which account posted this status? -	AccountID string   `bun:"type:CHAR(26),notnull"` -	Account   *Account `bun:"rel:belongs-to"` -	// AP uri of the owner of this status -	AccountURI string `bun:",nullzero"` -	// id of the status this status is a reply to -	InReplyToID string  `bun:"type:CHAR(26),nullzero"` -	InReplyTo   *Status `bun:"-"` -	// AP uri of the status this status is a reply to -	InReplyToURI string `bun:",nullzero"` -	// id of the account that this status replies to -	InReplyToAccountID string   `bun:"type:CHAR(26),nullzero"` -	InReplyToAccount   *Account `bun:"rel:belongs-to"` -	// id of the status this status is a boost of -	BoostOfID string  `bun:"type:CHAR(26),nullzero"` -	BoostOf   *Status `bun:"-"` -	// id of the account that owns the boosted status -	BoostOfAccountID string   `bun:"type:CHAR(26),nullzero"` -	BoostOfAccount   *Account `bun:"rel:belongs-to"` -	// cw string for this status -	ContentWarning string `bun:",nullzero"` -	// visibility entry for this status -	Visibility Visibility `bun:",notnull"` -	// mark the status as sensitive? -	Sensitive bool -	// what language is this status written in? -	Language string `bun:",nullzero"` -	// Which application was used to create this status? -	CreatedWithApplicationID string       `bun:"type:CHAR(26),nullzero"` -	CreatedWithApplication   *Application `bun:"rel:belongs-to"` -	// advanced visibility for this status -	VisibilityAdvanced *VisibilityAdvanced -	// What is the activitystreams type of this status? See: https://www.w3.org/TR/activitystreams-vocabulary/#object-types -	// Will probably almost always be Note but who knows!. -	ActivityStreamsType string `bun:",nullzero"` -	// Original text of the status without formatting -	Text string `bun:",nullzero"` -	// Has this status been pinned by its owner? -	Pinned bool +	ID                       string             `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"`                    // id of this item in the database +	CreatedAt                time.Time          `validate:"required" bun:",nullzero,notnull,default:current_timestamp"`                      // when was item created +	UpdatedAt                time.Time          `validate:"required" 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,required,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,required,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,required,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,required,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:",nullzero,notnull,default:false"`                                         // is this status from a local account? +	AccountID                string             `validate:"required,ulid" bun:"type:CHAR(26),nullzero,notnull"`                              // which account posted this status? +	Account                  *Account           `validate:"-" bun:"rel:belongs-to"`                                                          // account corresponding to accountID +	AccountURI               string             `validate:"required,url" bun:",nullzero,notnull"`                                            // activitypub uri of the owner of this status +	InReplyToID              string             `validate:"ulid,required_with=InReplyToURI InReplyToAccountID" bun:"type:CHAR(26),nullzero"` // id of the status this status replies to +	InReplyToURI             string             `validate:"required_with=InReplyToID InReplyToAccountID" bun:",nullzero"`                // activitypub uri of the status this status is a reply to +	InReplyToAccountID       string             `validate:"ulid,required_with=InReplyToID InReplyToURI" 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:"ulid,required_with=BoostOfAccountID" bun:"type:CHAR(26),nullzero"`                // id of the status this status is a boost of +	BoostOfAccountID         string             `validate:"ulid,required_with=BoostOfID" 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:",nullzero,notnull,default:false"`                                         // mark the status as sensitive? +	Language                 string             `validate:"-" bun:",nullzero"`                                                               // what language is this status written in? +	CreatedWithApplicationID string             `validate:"ulid,required_if=Local true" 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:",nullzero,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  `bun:"type:CHAR(26),unique:statustag,nullzero"` -	Status   *Status `bun:"rel:belongs-to"` -	TagID    string  `bun:"type:CHAR(26),unique:statustag,nullzero"` -	Tag      *Tag    `bun:"rel:belongs-to"` +	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  `bun:"type:CHAR(26),unique:statusemoji,nullzero"` -	Status   *Status `bun:"rel:belongs-to"` -	EmojiID  string  `bun:"type:CHAR(26),unique:statusemoji,nullzero"` -	Emoji    *Emoji  `bun:"rel:belongs-to"` +	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. @@ -137,12 +109,8 @@ const (  //  // If DIRECT is selected, boostable will be FALSE, and all other flags will be TRUE.  type VisibilityAdvanced struct { -	// This status will be federated beyond the local timeline(s) -	Federated bool `bun:"default:true"` -	// This status can be boosted/reblogged -	Boostable bool `bun:"default:true"` -	// This status can be replied to -	Replyable bool `bun:"default:true"` -	// This status can be liked/faved -	Likeable bool `bun:"default:true"` +	Federated bool `validate:"-" bun:",nullzero,notnull,default:true"` // This status will be federated beyond the local timeline(s) +	Boostable bool `validate:"-" bun:",nullzero,notnull,default:true"` // This status can be boosted/reblogged +	Replyable bool `validate:"-" bun:",nullzero,notnull,default:true"` // This status can be replied to +	Likeable  bool `validate:"-" bun:",nullzero,notnull,default:true"` // This status can be liked/faved  } diff --git a/internal/gtsmodel/status_test.go b/internal/gtsmodel/status_test.go new file mode 100644 index 000000000..a139fe715 --- /dev/null +++ b/internal/gtsmodel/status_test.go @@ -0,0 +1,161 @@ +/* +   GoToSocial +   Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org + +   This program is free software: you can redistribute it and/or modify +   it under the terms of the GNU Affero General Public License as published by +   the Free Software Foundation, either version 3 of the License, or +   (at your option) any later version. + +   This program is distributed in the hope that it will be useful, +   but WITHOUT ANY WARRANTY; without even the implied warranty of +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +   GNU Affero General Public License for more details. + +   You should have received a copy of the GNU Affero General Public License +   along with this program.  If not, see <http://www.gnu.org/licenses/>. +*/ + +package gtsmodel_test + +import ( +	"testing" +	"time" + +	"github.com/stretchr/testify/suite" +	"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:                  "<p>Test status! #hello</p>", +		AttachmentIDs:            []string{"01FEBBKZBY9H5FEP3PHVVAAGN1", "01FEBBM7S2R4WT6WWW22KN1PWE"}, +		Attachments:              nil, +		TagIDs:                   []string{"01FEBBNBMBSN1FESMZ1TCXNWYP"}, +		Tags:                     nil, +		MentionIDs:               nil, +		Mentions:                 nil, +		EmojiIDs:                 nil, +		Emojis:                   nil, +		Local:                    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: gtsmodel.ActivityStreamsNote, +		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 'required' 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 'required' 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 26dafa420..cabf90c06 100644 --- a/internal/gtsmodel/statusbookmark.go +++ b/internal/gtsmodel/statusbookmark.go @@ -20,18 +20,14 @@ package gtsmodel  import "time" -// StatusBookmark refers to one account having a 'bookmark' of the status of another account +// StatusBookmark refers to one account having a 'bookmark' of the status of another account.  type StatusBookmark struct { -	// id of this bookmark in the database -	ID string `bun:"type:CHAR(26),pk,notnull,unique"` -	// when was this bookmark created -	CreatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"` -	// id of the account that created ('did') the bookmarking -	AccountID string   `bun:"type:CHAR(26),notnull"` -	Account   *Account `bun:"rel:belongs-to"` -	// id the account owning the bookmarked status -	TargetAccountID string   `bun:"type:CHAR(26),notnull"` -	TargetAccount   *Account `bun:"rel:belongs-to"` -	// database id of the status that has been bookmarked -	StatusID string `bun:"type:CHAR(26),notnull"` +	ID              string    `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database +	CreatedAt       time.Time `validate:"required" 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/gtsmodel/statusbookmark_test.go b/internal/gtsmodel/statusbookmark_test.go new file mode 100644 index 000000000..7acd77698 --- /dev/null +++ b/internal/gtsmodel/statusbookmark_test.go @@ -0,0 +1,87 @@ +/* +   GoToSocial +   Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org + +   This program is free software: you can redistribute it and/or modify +   it under the terms of the GNU Affero General Public License as published by +   the Free Software Foundation, either version 3 of the License, or +   (at your option) any later version. + +   This program is distributed in the hope that it will be useful, +   but WITHOUT ANY WARRANTY; without even the implied warranty of +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +   GNU Affero General Public License for more details. + +   You should have received a copy of the GNU Affero General Public License +   along with this program.  If not, see <http://www.gnu.org/licenses/>. +*/ + +package 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.EqualError(err, "Key: 'StatusBookmark.CreatedAt' Error:Field validation for 'CreatedAt' failed on the 'required' tag") +} + +func TestStatusBookmarkValidateTestSuite(t *testing.T) { +	suite.Run(t, new(StatusBookmarkValidateTestSuite)) +} diff --git a/internal/gtsmodel/statusfave.go b/internal/gtsmodel/statusfave.go index 3b816af56..697112aba 100644 --- a/internal/gtsmodel/statusfave.go +++ b/internal/gtsmodel/statusfave.go @@ -22,19 +22,13 @@ import "time"  // StatusFave refers to a 'fave' or 'like' in the database, from one account, targeting the status of another account  type StatusFave struct { -	// id of this fave in the database -	ID string `bun:"type:CHAR(26),pk,notnull,unique"` -	// when was this fave created -	CreatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"` -	// id of the account that created ('did') the fave -	AccountID string   `bun:"type:CHAR(26),notnull"` -	Account   *Account `bun:"rel:belongs-to"` -	// id the account owning the faved status -	TargetAccountID string   `bun:"type:CHAR(26),notnull"` -	TargetAccount   *Account `bun:"rel:belongs-to"` -	// database id of the status that has been 'faved' -	StatusID string  `bun:"type:CHAR(26),notnull"` -	Status   *Status `bun:"rel:belongs-to"` -	// ActivityPub URI of this fave -	URI string `bun:",notnull"` +	ID              string    `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database +	CreatedAt       time.Time `validate:"required" 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/gtsmodel/statusfave_test.go b/internal/gtsmodel/statusfave_test.go new file mode 100644 index 000000000..65443b9fd --- /dev/null +++ b/internal/gtsmodel/statusfave_test.go @@ -0,0 +1,100 @@ +/* +   GoToSocial +   Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org + +   This program is free software: you can redistribute it and/or modify +   it under the terms of the GNU Affero General Public License as published by +   the Free Software Foundation, either version 3 of the License, or +   (at your option) any later version. + +   This program is distributed in the hope that it will be useful, +   but WITHOUT ANY WARRANTY; without even the implied warranty of +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +   GNU Affero General Public License for more details. + +   You should have received a copy of the GNU Affero General Public License +   along with this program.  If not, see <http://www.gnu.org/licenses/>. +*/ + +package 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.EqualError(err, "Key: 'StatusFave.CreatedAt' Error:Field validation for 'CreatedAt' failed on the 'required' tag") +} + +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/user.go b/internal/gtsmodel/user.go index 27089763d..4e7c4638a 100644 --- a/internal/gtsmodel/user.go +++ b/internal/gtsmodel/user.go @@ -29,7 +29,7 @@ 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:"required" bun:",nullzero,notnull,default:current_timestamp"`   // when was item created  	UpdatedAt              time.Time    `validate:"required" bun:",nullzero,notnull,default:current_timestamp"`   // when was item last updated -	Email                  string       `validate:"required_with=ConfirmedAt" bun:",nullzero,notnull,unique"`     // confirmed email address for this user, this should be unique -- only one email address registered per instance, multiple users per email are not supported +	Email                  string       `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. diff --git a/internal/gtsmodel/user_test.go b/internal/gtsmodel/user_test.go index c1a9bf849..0608f609a 100644 --- a/internal/gtsmodel/user_test.go +++ b/internal/gtsmodel/user_test.go @@ -33,7 +33,7 @@ func happyUser() *gtsmodel.User {  		LastEmailedAt:          time.Now(),  		ConfirmationToken:      "",  		ConfirmedAt:            time.Now(), -		ConfirmationSentAt:     time.Now(), +		ConfirmationSentAt:     time.Time{},  		UnconfirmedEmail:       "",  		Moderator:              false,  		Admin:                  false,  | 
