mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-31 08:22:27 -05:00 
			
		
		
		
	more work on struct validation
This commit is contained in:
		
					parent
					
						
							
								dc2e1bf9ab
							
						
					
				
			
			
				commit
				
					
						356d28fef9
					
				
			
		
					 12 changed files with 640 additions and 202 deletions
				
			
		|  | @ -25,127 +25,91 @@ import ( | ||||||
| // MediaAttachment represents a user-uploaded media attachment: an image/video/audio/gif that is | // 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. | // somewhere in storage and that can be retrieved and served by the router. | ||||||
| type MediaAttachment struct { | type MediaAttachment struct { | ||||||
| 	// ID of the attachment in the database | 	ID                string           `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"`                                                                           // id of this item in the database | ||||||
| 	ID string `bun:"type:CHAR(26),pk,notnull,unique"` | 	CreatedAt         time.Time        `validate:"required" bun:",nullzero,notnull,default:current_timestamp"`                                                                             // when was item created | ||||||
| 	// ID of the status to which this is attached | 	UpdatedAt         time.Time        `validate:"required" bun:",nullzero,notnull,default:current_timestamp"`                                                                             // when was item last updated | ||||||
| 	StatusID string `bun:"type:CHAR(26),nullzero"` | 	StatusID          string           `validate:"omitempty,ulid" bun:"type:CHAR(26),nullzero"`                                                                                            // ID of the status to which this is attached | ||||||
| 	// Where can the attachment be retrieved on *this* server | 	URL               string           `validate:"required_without=RemoteURL,omitempty,url" bun:",nullzero"`                                                                               // Where can the attachment be retrieved on *this* server | ||||||
| 	URL string `bun:",nullzero"` | 	RemoteURL         string           `validate:"required_without=URL,omitempty,url" bun:",nullzero"`                                                                                     // Where can the attachment be retrieved on a remote server (empty for local media) | ||||||
| 	// 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) | ||||||
| 	RemoteURL string `bun:",nullzero"` | 	FileMeta          FileMeta         `validate:"required" bun:",nullzero,notnull"`                                                                                                       // Metadata about the file | ||||||
| 	// When was the attachment created | 	AccountID         string           `validate:"required,ulid" bun:"type:CHAR(26),nullzero,notnull"`                                                                                     // To which account does this attachment belong | ||||||
| 	CreatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"` | 	Account           *Account         `validate:"-" bun:"rel:has-one"`                                                                                                                    // Account corresponding to accountID | ||||||
| 	// When was the attachment last updated | 	Description       string           `validate:"-" bun:",nullzero"`                                                                                                                      // Description of the attachment (for screenreaders) | ||||||
| 	UpdatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"` | 	ScheduledStatusID string           `validate:"omitempty,ulid" bun:"type:CHAR(26),nullzero"`                                                                                            // To which scheduled status does this attachment belong | ||||||
| 	// Type of file (image/gif/audio/video) | 	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 | ||||||
| 	Type FileType `bun:",notnull"` | 	Processing        ProcessingStatus `validate:"oneof=0 1 2 666" bun:",notnull,default:2"`                                                                                      // What is the processing status of this attachment | ||||||
| 	// Metadata about the file | 	File              File             `validate:"required" bun:",notnull,nullzero"`                                                                                                       // metadata for the whole file | ||||||
| 	FileMeta FileMeta | 	Thumbnail         Thumbnail        `validate:"required" bun:",notnull,nullzero"`                                                                                                       // small image thumbnail derived from a larger image, video, or audio file. | ||||||
| 	// To which account does this attachment belong | 	Avatar            bool             `validate:"-" bun:",notnull,default:false"`                                                                                                         // Is this attachment being used as an avatar? | ||||||
| 	AccountID string   `bun:"type:CHAR(26),notnull"` | 	Header            bool             `validate:"-" bun:",notnull,default:false"`                                                                                                         // Is this attachment being used as a header? | ||||||
| 	Account   *Account `bun:"rel:has-one"` |  | ||||||
| 	// Description of the attachment (for screenreaders) |  | ||||||
| 	Description string `bun:",nullzero"` |  | ||||||
| 	// To which scheduled status does this attachment belong |  | ||||||
| 	ScheduledStatusID string `bun:"type:CHAR(26),nullzero"` |  | ||||||
| 	// What is the generated blurhash of this attachment |  | ||||||
| 	Blurhash string `bun:",nullzero"` |  | ||||||
| 	// What is the processing status of this attachment |  | ||||||
| 	Processing ProcessingStatus |  | ||||||
| 	// metadata for the whole file |  | ||||||
| 	File File |  | ||||||
| 	// small image thumbnail derived from a larger image, video, or audio file. |  | ||||||
| 	Thumbnail Thumbnail |  | ||||||
| 	// Is this attachment being used as an avatar? |  | ||||||
| 	Avatar bool |  | ||||||
| 	// Is this attachment being used as a header? |  | ||||||
| 	Header bool |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // File refers to the metadata for the whole file | // File refers to the metadata for the whole file | ||||||
| type File struct { | type File struct { | ||||||
| 	// What is the path of the file in storage. | 	Path        string    `validate:"required,file" bun:",nullzero,notnull"`                      // Path of the file in storage. | ||||||
| 	Path string `bun:",nullzero"` | 	ContentType string    `validate:"required" bun:",nullzero,notnull"`                           // MIME content type of the file. | ||||||
| 	// What is the MIME content type of the file. | 	FileSize    int       `validate:"required" bun:",nullzero,notnull"`                           // File size in bytes | ||||||
| 	ContentType string `bun:",nullzero"` | 	UpdatedAt   time.Time `validate:"required" bun:",nullzero,notnull,default:current_timestamp"` // When was the file last updated. | ||||||
| 	// What is the size of the file in bytes. |  | ||||||
| 	FileSize int |  | ||||||
| 	// When was the file last updated. |  | ||||||
| 	UpdatedAt time.Time `bun:",notnull,default:current_timestamp"` |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Thumbnail refers to a small image thumbnail derived from a larger image, video, or audio file. | // Thumbnail refers to a small image thumbnail derived from a larger image, video, or audio file. | ||||||
| type Thumbnail struct { | type Thumbnail struct { | ||||||
| 	// What is the path of the file in storage | 	Path        string    `validate:"required,file" bun:",nullzero,notnull"`                      // Path of the file in storage. | ||||||
| 	Path string `bun:",nullzero"` | 	ContentType string    `validate:"required" bun:",nullzero,notnull"`                           // MIME content type of the file. | ||||||
| 	// What is the MIME content type of the file. | 	FileSize    int       `validate:"required" bun:",nullzero,notnull"`                           // File size in bytes | ||||||
| 	ContentType string `bun:",nullzero"` | 	UpdatedAt   time.Time `validate:"required" bun:",nullzero,notnull,default:current_timestamp"` // When was the file last updated. | ||||||
| 	// What is the size of the file in bytes | 	URL         string    `validate:"required_without=RemoteURL,omitempty,url" bun:",nullzero"`   // What is the URL of the thumbnail on the local server | ||||||
| 	FileSize int | 	RemoteURL   string    `validate:"required_without=URL,omitempty,url" bun:",nullzero"`         // What is the remote URL of the thumbnail (empty for local media) | ||||||
| 	// When was the file last updated |  | ||||||
| 	UpdatedAt time.Time `bun:",notnull,default:current_timestamp"` |  | ||||||
| 	// What is the URL of the thumbnail on the local server |  | ||||||
| 	URL string `bun:",nullzero"` |  | ||||||
| 	// What is the remote URL of the thumbnail (empty for local media) |  | ||||||
| 	RemoteURL string `bun:",nullzero"` |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // ProcessingStatus refers to how far along in the processing stage the attachment is. | // ProcessingStatus refers to how far along in the processing stage the attachment is. | ||||||
| type ProcessingStatus int | type ProcessingStatus int | ||||||
| 
 | 
 | ||||||
| const ( | const ( | ||||||
| 	// ProcessingStatusReceived indicates the attachment has been received and is awaiting processing. No thumbnail available yet. | 	ProcessingStatusReceived   ProcessingStatus = 0   // ProcessingStatusReceived indicates the attachment has been received and is awaiting processing. No thumbnail available yet. | ||||||
| 	ProcessingStatusReceived ProcessingStatus = 0 | 	ProcessingStatusProcessing ProcessingStatus = 1   // ProcessingStatusProcessing indicates the attachment is currently being processed. Thumbnail is available but full media is not. | ||||||
| 	// 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. | ||||||
| 	ProcessingStatusProcessing ProcessingStatus = 1 | 	ProcessingStatusError      ProcessingStatus = 666 // ProcessingStatusError indicates something went wrong processing the attachment and it won't be tried again--these can be deleted. | ||||||
| 	// ProcessingStatusProcessed indicates the attachment has been fully processed and is ready to be served. |  | ||||||
| 	ProcessingStatusProcessed ProcessingStatus = 2 |  | ||||||
| 	// ProcessingStatusError indicates something went wrong processing the attachment and it won't be tried again--these can be deleted. |  | ||||||
| 	ProcessingStatusError ProcessingStatus = 666 |  | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // FileType refers to the file type of the media attaachment. | // FileType refers to the file type of the media attaachment. | ||||||
| type FileType string | type FileType string | ||||||
| 
 | 
 | ||||||
| const ( | const ( | ||||||
| 	// FileTypeImage is for jpegs and pngs | 	FileTypeImage   FileType = "Image"   // FileTypeImage is for jpegs and pngs | ||||||
| 	FileTypeImage FileType = "Image" | 	FileTypeGif     FileType = "Gif"     // FileTypeGif is for native gifs and soundless videos that have been converted to gifs | ||||||
| 	// 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) | ||||||
| 	FileTypeGif FileType = "Gif" | 	FileTypeVideo   FileType = "Video"   // FileTypeVideo is for files with audio + visual | ||||||
| 	// FileTypeAudio is for audio-only files (no video) | 	FileTypeUnknown FileType = "Unknown" // FileTypeUnknown is for unknown file types (surprise surprise!) | ||||||
| 	FileTypeAudio FileType = "Audio" |  | ||||||
| 	// FileTypeVideo is for files with audio + visual |  | ||||||
| 	FileTypeVideo FileType = "Video" |  | ||||||
| 	// FileTypeUnknown is for unknown file types (surprise surprise!) |  | ||||||
| 	FileTypeUnknown FileType = "Unknown" |  | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // FileMeta describes metadata about the actual contents of the file. | // FileMeta describes metadata about the actual contents of the file. | ||||||
| type FileMeta struct { | type FileMeta struct { | ||||||
| 	Original Original | 	Original Original `validate:"required"` | ||||||
| 	Small    Small | 	Small    Small | ||||||
| 	Focus    Focus | 	Focus    Focus | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Small can be used for a thumbnail of any media type | // Small can be used for a thumbnail of any media type | ||||||
| type Small struct { | type Small struct { | ||||||
| 	Width  int | 	Width  int     `validate:"required_with=Height Size Aspect"`  // width in pixels | ||||||
| 	Height int | 	Height int     `validate:"required_with=Width Size Aspect"`   // height in pixels | ||||||
| 	Size   int | 	Size   int     `validate:"required_with=Width Height Aspect"` // size in pixels (width * height) | ||||||
| 	Aspect float64 | 	Aspect float64 `validate:"required_with=Widhth Height Size"`  // aspect ratio (width / height) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Original can be used for original metadata for any media type | // Original can be used for original metadata for any media type | ||||||
| type Original struct { | type Original struct { | ||||||
| 	Width  int | 	Width  int     `validate:"required_with=Height Size Aspect"`  // width in pixels | ||||||
| 	Height int | 	Height int     `validate:"required_with=Width Size Aspect"`   // height in pixels | ||||||
| 	Size   int | 	Size   int     `validate:"required_with=Width Height Aspect"` // size in pixels (width * height) | ||||||
| 	Aspect float64 | 	Aspect float64 `validate:"required_with=Widhth Height Size"`  // aspect ratio (width / height) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Focus describes the 'center' of the image for display purposes. | // Focus describes the 'center' of the image for display purposes. | ||||||
| // X and Y should each be between -1 and 1 | // X and Y should each be between -1 and 1 | ||||||
| type Focus struct { | type Focus struct { | ||||||
| 	X float32 | 	X float32 `validate:"omitempty,max=1,min=-1"` | ||||||
| 	Y float32 | 	Y float32 `validate:"omitempty,max=1,min=-1"` | ||||||
| } | } | ||||||
|  |  | ||||||
							
								
								
									
										217
									
								
								internal/gtsmodel/mediaattachment_test.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										217
									
								
								internal/gtsmodel/mediaattachment_test.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,217 @@ | ||||||
|  | /* | ||||||
|  |    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 ( | ||||||
|  | 	"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 TestMediaAttachmentValidateTestSuite(t *testing.T) { | ||||||
|  | 	suite.Run(t, new(MediaAttachmentValidateTestSuite)) | ||||||
|  | } | ||||||
|  | @ -22,25 +22,17 @@ import "time" | ||||||
| 
 | 
 | ||||||
| // Mention refers to the 'tagging' or 'mention' of a user within a status. | // Mention refers to the 'tagging' or 'mention' of a user within a status. | ||||||
| type Mention struct { | type Mention struct { | ||||||
| 	// ID of this mention in the database | 	ID               string    `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database | ||||||
| 	ID string `bun:"type:CHAR(26),pk,notnull,unique"` | 	CreatedAt        time.Time `validate:"required" bun:",nullzero,notnull,default:current_timestamp"`   // when was item created | ||||||
| 	// ID of the status this mention originates from | 	UpdatedAt        time.Time `validate:"required" bun:",nullzero,notnull,default:current_timestamp"`   // when was item last updated | ||||||
| 	StatusID string  `bun:"type:CHAR(26),notnull,nullzero"` | 	StatusID         string    `validate:"required,ulid" bun:"type:CHAR(26),nullzero,notnull"`           // ID of the status this mention originates from | ||||||
| 	Status   *Status `bun:"rel:belongs-to"` | 	Status           *Status   `validate:"-" bun:"rel:belongs-to"`                                       // status referred to by statusID | ||||||
| 	// When was this mention created? | 	OriginAccountID  string    `validate:"required,ulid" bun:"type:CHAR(26),nullzero,notnull"`           // ID of the mention creator account | ||||||
| 	CreatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"` | 	OriginAccountURI string    `validate:"url" bun:",nullzero,notnull"`                                  // ActivityPub URI of the originator/creator of the mention | ||||||
| 	// When was this mention last updated? | 	OriginAccount    *Account  `validate:"-" bun:"rel:belongs-to"`                                       // account referred to by originAccountID | ||||||
| 	UpdatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"` | 	TargetAccountID  string    `validate:"required,ulid" bun:"type:CHAR(26),nullzero,notnull"`           // Mention target/receiver account ID | ||||||
| 	// What's the internal account ID of the originator of the mention? | 	TargetAccount    *Account  `validate:"-" bun:"rel:belongs-to"`                                       // account referred to by targetAccountID | ||||||
| 	OriginAccountID string   `bun:"type:CHAR(26),notnull,nullzero"` | 	Silent           bool      `validate:"-" bun:",notnull,default:false"`                               // Prevent this mention from generating a notification? | ||||||
| 	OriginAccount   *Account `bun:"rel:belongs-to"` |  | ||||||
| 	// What's the AP URI of the originator of the mention? |  | ||||||
| 	OriginAccountURI string `bun:",notnull"` |  | ||||||
| 	// What's the internal account ID of the mention target? |  | ||||||
| 	TargetAccountID string   `bun:"type:CHAR(26),notnull,nullzero"` |  | ||||||
| 	TargetAccount   *Account `bun:"rel:belongs-to"` |  | ||||||
| 	// Prevent this mention from generating a notification? |  | ||||||
| 	Silent bool |  | ||||||
| 
 | 
 | ||||||
| 	/* | 	/* | ||||||
| 		NON-DATABASE CONVENIENCE FIELDS | 		NON-DATABASE CONVENIENCE FIELDS | ||||||
|  | @ -54,15 +46,14 @@ type Mention struct { | ||||||
| 	// @whatever_username@example.org | 	// @whatever_username@example.org | ||||||
| 	// | 	// | ||||||
| 	// This will not be put in the database, it's just for convenience. | 	// This will not be put in the database, it's just for convenience. | ||||||
| 	NameString string `bun:"-"` | 	NameString string `validate:"-" bun:"-"` | ||||||
| 	// TargetAccountURI is the AP ID (uri) of the user mentioned. | 	// TargetAccountURI is the AP ID (uri) of the user mentioned. | ||||||
| 	// | 	// | ||||||
| 	// This will not be put in the database, it's just for convenience. | 	// This will not be put in the database, it's just for convenience. | ||||||
| 	TargetAccountURI string `bun:"-"` | 	TargetAccountURI string `validate:"-" bun:"-"` | ||||||
| 	// TargetAccountURL is the web url of the user mentioned. | 	// TargetAccountURL is the web url of the user mentioned. | ||||||
| 	// | 	// | ||||||
| 	// This will not be put in the database, it's just for convenience. | 	// This will not be put in the database, it's just for convenience. | ||||||
| 	TargetAccountURL string `bun:"-"` | 	TargetAccountURL string `validate:"-" bun:"-"` | ||||||
| 	// A pointer to the gtsmodel account of the mentioned account. | 	// A pointer to the gtsmodel account of the mentioned account. | ||||||
| 
 |  | ||||||
| } | } | ||||||
|  |  | ||||||
							
								
								
									
										101
									
								
								internal/gtsmodel/mention_test.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								internal/gtsmodel/mention_test.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 <http://www.gnu.org/licenses/>. | ||||||
|  | */ | ||||||
|  | 
 | ||||||
|  | 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.EqualError(err, "Key: 'Mention.CreatedAt' Error:Field validation for 'CreatedAt' failed on the 'required' tag") | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestMentionValidateTestSuite(t *testing.T) { | ||||||
|  | 	suite.Run(t, new(MentionValidateTestSuite)) | ||||||
|  | } | ||||||
|  | @ -22,41 +22,27 @@ import "time" | ||||||
| 
 | 
 | ||||||
| // Notification models an alert/notification sent to an account about something like a reblog, like, new follow request, etc. | // Notification models an alert/notification sent to an account about something like a reblog, like, new follow request, etc. | ||||||
| type Notification struct { | type Notification struct { | ||||||
| 	// ID of this notification in the database | 	ID               string           `validate:"ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"`                                                                                                                                             // id of this item in the database | ||||||
| 	ID string `bun:"type:CHAR(26),pk,notnull"` | 	CreatedAt        time.Time        `validate:"required" bun:",nullzero,notnull,default:current_timestamp"`                                                                                                                                      // when was item created | ||||||
| 	// Type of this notification | 	NotificationType NotificationType `validate:"oneof=follow follow_request mention reblog favourite poll status" bun:",nullzero,notnull"`                                                                                                        // Type of this notification | ||||||
| 	NotificationType NotificationType `bun:",notnull"` | 	TargetAccountID  string           `validate:"ulid" bun:"type:CHAR(26),nullzero,notnull"`                                                                                                                                                       // Which account does this notification target (ie., who will receive the notification?) | ||||||
| 	// Creation time of this notification | 	TargetAccount    *Account         `validate:"-" bun:"rel:belongs-to"`                                                                                                                                                                          // Which account performed the action that created this notification? | ||||||
| 	CreatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"` | 	OriginAccountID  string           `validate:"ulid" bun:"type:CHAR(26),nullzero,notnull"`                                                                                                                                                       // ID of the account that performed the action that created the notification. | ||||||
| 	// Which account does this notification target (ie., who will receive the notification?) | 	OriginAccount    *Account         `validate:"-" bun:"rel:belongs-to"`                                                                                                                                                                          // Account corresponding to originAccountID | ||||||
| 	TargetAccountID string   `bun:"type:CHAR(26),notnull"` | 	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? | ||||||
| 	TargetAccount   *Account `bun:"rel:belongs-to"` | 	Status           *Status          `validate:"-" bun:"rel:belongs-to"`                                                                                                                                                                          // Status corresponding to statusID | ||||||
| 	// Which account performed the action that created this notification? | 	Read             bool             `validate:"-" bun:",notnull,default:false"`                                                                                                                                                         // Notification has been seen/read | ||||||
| 	OriginAccountID string   `bun:"type:CHAR(26),notnull"` |  | ||||||
| 	OriginAccount   *Account `bun:"rel:belongs-to"` |  | ||||||
| 	// If the notification pertains to a status, what is the database ID of that status? |  | ||||||
| 	StatusID string  `bun:"type:CHAR(26),nullzero"` |  | ||||||
| 	Status   *Status `bun:"rel:belongs-to"` |  | ||||||
| 	// Has this notification been read already? |  | ||||||
| 	Read bool |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // NotificationType describes the reason/type of this notification. | // NotificationType describes the reason/type of this notification. | ||||||
| type NotificationType string | type NotificationType string | ||||||
| 
 | 
 | ||||||
| const ( | const ( | ||||||
| 	// NotificationFollow -- someone followed you | 	NotificationFollow        NotificationType = "follow"         // NotificationFollow -- someone followed you | ||||||
| 	NotificationFollow NotificationType = "follow" | 	NotificationFollowRequest NotificationType = "follow_request" // NotificationFollowRequest -- someone requested to follow you | ||||||
| 	// NotificationFollowRequest -- someone requested to follow you | 	NotificationMention       NotificationType = "mention"        // NotificationMention -- someone mentioned you in their status | ||||||
| 	NotificationFollowRequest NotificationType = "follow_request" | 	NotificationReblog        NotificationType = "reblog"         // NotificationReblog -- someone boosted one of your statuses | ||||||
| 	// NotificationMention -- someone mentioned you in their status | 	NotificationFave          NotificationType = "favourite"      // NotificationFave -- someone faved/liked one of your statuses | ||||||
| 	NotificationMention NotificationType = "mention" | 	NotificationPoll          NotificationType = "poll"           // NotificationPoll -- a poll you voted in or created has ended | ||||||
| 	// NotificationReblog -- someone boosted one of your statuses | 	NotificationStatus        NotificationType = "status"         // NotificationStatus -- someone you enabled notifications for has posted a status. | ||||||
| 	NotificationReblog NotificationType = "reblog" |  | ||||||
| 	// NotificationFave -- someone faved/liked one of your statuses |  | ||||||
| 	NotificationFave NotificationType = "favourite" |  | ||||||
| 	// NotificationPoll -- a poll you voted in or created has ended |  | ||||||
| 	NotificationPoll NotificationType = "poll" |  | ||||||
| 	// NotificationStatus -- someone you enabled notifications for has posted a status. |  | ||||||
| 	NotificationStatus NotificationType = "status" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
							
								
								
									
										97
									
								
								internal/gtsmodel/notification_test.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								internal/gtsmodel/notification_test.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 <http://www.gnu.org/licenses/>. | ||||||
|  | */ | ||||||
|  | 
 | ||||||
|  | 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.EqualError(err, "Key: 'Notification.CreatedAt' Error:Field validation for 'CreatedAt' failed on the 'required' tag") | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestNotificationValidateTestSuite(t *testing.T) { | ||||||
|  | 	suite.Run(t, new(NotificationValidateTestSuite)) | ||||||
|  | } | ||||||
							
								
								
									
										87
									
								
								internal/gtsmodel/routersession_test.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								internal/gtsmodel/routersession_test.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -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" | ||||||
|  | 
 | ||||||
|  | 	"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)) | ||||||
|  | } | ||||||
|  | @ -30,37 +30,37 @@ type Status struct { | ||||||
| 	URI                      string             `validate:"required,url" bun:",unique,nullzero,notnull"`                                               // activitypub URI of this status | 	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 | 	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 | 	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 | 	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 | 	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 | 	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 | 	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 | 	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 | 	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 | 	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 | 	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? | 	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? | 	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 | 	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 | 	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 | 	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" bun:",nullzero"`                // activitypub uri of the status this status is a reply 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:"ulid,required_with=InReplyToID InReplyToURI" bun:"type:CHAR(26),nullzero"`        // id of the account that this status replies 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 | 	InReplyTo                *Status            `validate:"-" bun:"-"`                                                                                 // status corresponding to inReplyToID | ||||||
| 	InReplyToAccount         *Account           `validate:"-" bun:"rel:belongs-to"`                                                                    // account corresponding to inReplyToAccountID | 	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 | 	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:"ulid,required_with=BoostOfID" bun:"type:CHAR(26),nullzero"`                       // id of the account that owns the boosted status | 	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 | 	BoostOf                  *Status            `validate:"-" bun:"-"`                                                                                 // status that corresponds to boostOfID | ||||||
| 	BoostOfAccount           *Account           `validate:"-" bun:"rel:belongs-to"`                                                                    // account that corresponds to boostOfAccountID | 	BoostOfAccount           *Account           `validate:"-" bun:"rel:belongs-to"`                                                                    // account that corresponds to boostOfAccountID | ||||||
| 	ContentWarning           string             `validate:"-" bun:",nullzero"`                                                                         // cw string for this status | 	ContentWarning           string             `validate:"-" bun:",nullzero"`                                                                         // cw string for this status | ||||||
| 	Visibility               Visibility         `validate:"-" bun:",nullzero,notnull"`                                                                 // visibility entry 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? | 	Sensitive                bool               `validate:"-" bun:",notnull,default:false"`                                                            // mark the status as sensitive? | ||||||
| 	Language                 string             `validate:"-" bun:",nullzero"`                                                                         // what language is this status written in? | 	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? | 	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 | 	CreatedWithApplication   *Application       `validate:"-" bun:"rel:belongs-to"`                                                                    // application corresponding to createdWithApplicationID | ||||||
| 	VisibilityAdvanced       VisibilityAdvanced `validate:"required" bun:",nullzero,notnull" `                                                         // advanced visibility for this status | 	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!. | 	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 | 	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? | 	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. | // StatusToTag is an intermediate struct to facilitate the many2many relationship between a status and one or more tags. | ||||||
|  | @ -109,8 +109,8 @@ const ( | ||||||
| // | // | ||||||
| // If DIRECT is selected, boostable will be FALSE, and all other flags will be TRUE. | // If DIRECT is selected, boostable will be FALSE, and all other flags will be TRUE. | ||||||
| type VisibilityAdvanced struct { | type VisibilityAdvanced struct { | ||||||
| 	Federated bool `validate:"-" bun:",nullzero,notnull,default:true"` // This status will be federated beyond the local timeline(s) | 	Federated bool `validate:"-" bun:",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 | 	Boostable bool `validate:"-" bun:",notnull,default:true"` // This status can be boosted/reblogged | ||||||
| 	Replyable bool `validate:"-" bun:",nullzero,notnull,default:true"` // This status can be replied to | 	Replyable bool `validate:"-" bun:",notnull,default:true"` // This status can be replied to | ||||||
| 	Likeable  bool `validate:"-" bun:",nullzero,notnull,default:true"` // This status can be liked/faved | 	Likeable  bool `validate:"-" bun:",notnull,default:true"` // This status can be liked/faved | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -101,7 +101,7 @@ func (suite *StatusValidateTestSuite) TestValidateStatusAttachmentIDs() { | ||||||
| 
 | 
 | ||||||
| 	s.AttachmentIDs[0] = "" | 	s.AttachmentIDs[0] = "" | ||||||
| 	err := gtsmodel.ValidateStruct(*s) | 	err := gtsmodel.ValidateStruct(*s) | ||||||
| 	suite.EqualError(err, "Key: 'Status.AttachmentIDs[0]' Error:Field validation for 'AttachmentIDs[0]' failed on the 'required' tag") | 	suite.EqualError(err, "Key: 'Status.AttachmentIDs[0]' Error:Field validation for 'AttachmentIDs[0]' failed on the 'ulid' tag") | ||||||
| 
 | 
 | ||||||
| 	s.AttachmentIDs[0] = "01FE96W293ZPRG9FQQP48HK8N001FE96W32AT24VYBGM12WN3GKB" | 	s.AttachmentIDs[0] = "01FE96W293ZPRG9FQQP48HK8N001FE96W32AT24VYBGM12WN3GKB" | ||||||
| 	err = gtsmodel.ValidateStruct(*s) | 	err = gtsmodel.ValidateStruct(*s) | ||||||
|  | @ -109,7 +109,7 @@ func (suite *StatusValidateTestSuite) TestValidateStatusAttachmentIDs() { | ||||||
| 
 | 
 | ||||||
| 	s.AttachmentIDs[1] = "" | 	s.AttachmentIDs[1] = "" | ||||||
| 	err = gtsmodel.ValidateStruct(*s) | 	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") | 	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{} | 	s.AttachmentIDs = []string{} | ||||||
| 	err = gtsmodel.ValidateStruct(*s) | 	err = gtsmodel.ValidateStruct(*s) | ||||||
|  |  | ||||||
|  | @ -27,8 +27,8 @@ type Tag struct { | ||||||
| 	UpdatedAt              time.Time `validate:"required" bun:",nullzero,notnull,default:current_timestamp"`   // when was item last updated | 	UpdatedAt              time.Time `validate:"required" bun:",nullzero,notnull,default:current_timestamp"`   // when was item last updated | ||||||
| 	URL                    string    `validate:"required,url" bun:",nullzero,notnull"`                         // Href of this tag, eg https://example.org/tags/somehashtag | 	URL                    string    `validate:"required,url" bun:",nullzero,notnull"`                         // Href 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 | 	Name                   string    `validate:"required" bun:",unique,nullzero,notnull"`                      // name of this tag -- the tag without the hash part | ||||||
| 	FirstSeenFromAccountID string    `validate:"ulid" bun:"type:CHAR(26),nullzero"`                            // Which account ID is the first one we saw using this tag? | 	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:",nullzero,notnull,default:true"`                       // can our instance users use this tag? | 	Useable                bool      `validate:"-" bun:",notnull,default:true"`                                // can our instance users use this tag? | ||||||
| 	Listable               bool      `validate:"-" bun:",nullzero,notnull,default:true"`                       // can our instance users look up this tag? | 	Listable               bool      `validate:"-" bun:",notnull,default:true"`                                // can our instance users look up this tag? | ||||||
| 	LastStatusAt           time.Time `validate:"required" bun:",nullzero,notnull,default:current_timestamp"`   // when was this tag last used? | 	LastStatusAt           time.Time `validate:"required" bun:",nullzero,notnull,default:current_timestamp"`   // when was this tag last used? | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -39,28 +39,28 @@ type User struct { | ||||||
| 	LastSignInAt           time.Time    `validate:"-" bun:",nullzero"`                                            // When did this user last sign in? | 	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? | 	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? | 	SignInCount            int          `validate:"-" bun:",nullzero,notnull,default:0"`                          // How many times has this user signed in? | ||||||
| 	InviteID               string       `validate:"ulid" bun:"type:CHAR(26),nullzero"`                            // id of the user who invited this user (who let this joker 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? | 	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? | 	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? | 	Locale                 string       `validate:"-" bun:",nullzero"`                                            // In what timezone/locale is this user located? | ||||||
| 	CreatedByApplicationID string       `validate:"ulid" bun:"type:CHAR(26),nullzero,notnull"`                    // Which application id created this user? See gtsmodel.Application | 	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. | 	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. | 	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? | 	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? | 	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 | 	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 | 	UnconfirmedEmail       string       `validate:"required_without=Email" bun:",nullzero"`                       // Email address that hasn't yet been confirmed | ||||||
| 	Moderator              bool         `validate:"-" bun:",nullzero,notnull,default:false"`                      // Is this user a moderator? | 	Moderator              bool         `validate:"-" bun:",notnull,default:false"`                               // Is this user a moderator? | ||||||
| 	Admin                  bool         `validate:"-" bun:",nullzero,notnull,default:false"`                      // Is this user an admin? | 	Admin                  bool         `validate:"-" bun:",notnull,default:false"`                               // Is this user an admin? | ||||||
| 	Disabled               bool         `validate:"-" bun:",nullzero,notnull,default:false"`                      // Is this user disabled from posting? | 	Disabled               bool         `validate:"-" bun:",notnull,default:false"`                               // Is this user disabled from posting? | ||||||
| 	Approved               bool         `validate:"-" bun:",nullzero,notnull,default:false"`                      // Has this user been approved by a moderator? | 	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 | 	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? | 	ResetPasswordSentAt    time.Time    `validate:"required_with=ResetPasswordToken" bun:",nullzero"`             // When did we email the user their reset-password email? | ||||||
| 
 | 
 | ||||||
| 	EncryptedOTPSecret     string    `validate:"-" bun:",nullzero"` | 	EncryptedOTPSecret     string    `validate:"-" bun:",nullzero"` | ||||||
| 	EncryptedOTPSecretIv   string    `validate:"-" bun:",nullzero"` | 	EncryptedOTPSecretIv   string    `validate:"-" bun:",nullzero"` | ||||||
| 	EncryptedOTPSecretSalt string    `validate:"-" bun:",nullzero"` | 	EncryptedOTPSecretSalt string    `validate:"-" bun:",nullzero"` | ||||||
| 	OTPRequiredForLogin    bool      `validate:"-" bun:",nullzero"` | 	OTPRequiredForLogin    bool      `validate:"-" bun:",notnull,default:false"` | ||||||
| 	OTPBackupCodes         []string  `validate:"-" bun:",nullzero"` | 	OTPBackupCodes         []string  `validate:"-" bun:",nullzero"` | ||||||
| 	ConsumedTimestamp      int       `validate:"-" bun:",nullzero"` | 	ConsumedTimestamp      int       `validate:"-" bun:",nullzero"` | ||||||
| 	RememberToken          string    `validate:"-" bun:",nullzero"` | 	RememberToken          string    `validate:"-" bun:",nullzero"` | ||||||
|  |  | ||||||
|  | @ -32,20 +32,15 @@ const ( | ||||||
| 	InvalidValidationPanic = "validate function was passed invalid item" | 	InvalidValidationPanic = "validate function was passed invalid item" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| var ulidValidator = func(fl validator.FieldLevel) bool { | func ulidValidator(fl validator.FieldLevel) bool { | ||||||
| 	value, kind, _ := fl.ExtractType(fl.Field()) | 	field := fl.Field() | ||||||
| 
 | 
 | ||||||
| 	if kind != reflect.String { | 	switch field.Kind() { | ||||||
|  | 	case reflect.String: | ||||||
|  | 		return util.ValidateULID(field.String()) | ||||||
|  | 	default: | ||||||
| 		return false | 		return false | ||||||
| 	} | 	} | ||||||
| 
 |  | ||||||
| 	// we want either an empty string, or a proper ULID, nothing else |  | ||||||
| 	// if the string is empty, the `required` tag will take care of it so we don't need to worry about it here |  | ||||||
| 	s := value.String() |  | ||||||
| 	if len(s) == 0 { |  | ||||||
| 		return true |  | ||||||
| 	} |  | ||||||
| 	return util.ValidateULID(s) |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func init() { | func init() { | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue