mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-31 14:42:26 -05:00 
			
		
		
		
	Merge pull request #186 from superseriousbusiness/struct_validation
Struct validation
This commit is contained in:
		
				commit
				
					
						25edd57eaf
					
				
			
		
					 133 changed files with 5030 additions and 1476 deletions
				
			
		|  | @ -130,9 +130,13 @@ The following libraries and frameworks are used by GoToSocial, with gratitude  | ||||||
| * [go-fed/activity](https://github.com/go-fed/activity); Golang ActivityPub/ActivityStreams library. [BSD-3-Clause License](https://spdx.org/licenses/BSD-3-Clause.html). | * [go-fed/activity](https://github.com/go-fed/activity); Golang ActivityPub/ActivityStreams library. [BSD-3-Clause License](https://spdx.org/licenses/BSD-3-Clause.html). | ||||||
| * [go-fed/httpsig](https://github.com/go-fed/httpsig); secure HTTP signature library. [BSD-3-Clause License](https://spdx.org/licenses/BSD-3-Clause.html). | * [go-fed/httpsig](https://github.com/go-fed/httpsig); secure HTTP signature library. [BSD-3-Clause License](https://spdx.org/licenses/BSD-3-Clause.html). | ||||||
| * [google/uuid](https://github.com/google/uuid); UUID generation. [BSD-3-Clause License](https://spdx.org/licenses/BSD-3-Clause.html) | * [google/uuid](https://github.com/google/uuid); UUID generation. [BSD-3-Clause License](https://spdx.org/licenses/BSD-3-Clause.html) | ||||||
|  | * [go-playground/validator](https://github.com/go-playground/validator); struct validation. [MIT License](https://spdx.org/licenses/MIT.html) | ||||||
| * [gorilla/websocket](https://github.com/gorilla/websocket); Websocket connectivity. [BSD-2-Clause License](https://spdx.org/licenses/BSD-2-Clause.html). | * [gorilla/websocket](https://github.com/gorilla/websocket); Websocket connectivity. [BSD-2-Clause License](https://spdx.org/licenses/BSD-2-Clause.html). | ||||||
| * [h2non/filetype](https://github.com/h2non/filetype); filetype checking. [MIT License](https://spdx.org/licenses/MIT.html). | * [h2non/filetype](https://github.com/h2non/filetype); filetype checking. [MIT License](https://spdx.org/licenses/MIT.html). | ||||||
| * [jackc/pgx](https://github.com/jackc/pgx); Postgres driver. [MIT License](https://spdx.org/licenses/MIT.html). | * [jackc/pgx](https://github.com/jackc/pgx); Postgres driver. [MIT License](https://spdx.org/licenses/MIT.html). | ||||||
|  | * [modernc.org/sqlite](https://gitlab.com/cznic/sqlite); cgo-free port of SQLite. [Other License](https://gitlab.com/cznic/sqlite/-/blob/master/LICENSE). | ||||||
|  |   * [modernc.org/ccgo](https://gitlab.com/cznic/ccgo); c99 AST -> Go translater. [BSD-3-Clause License](https://spdx.org/licenses/BSD-3-Clause.html). | ||||||
|  |   * [modernc.org/libc](https://gitlab.com/cznic/libc); C-runtime services. [BSD-3-Clause License](https://spdx.org/licenses/BSD-3-Clause.html). | ||||||
| * [microcosm-cc/bluemonday](https://github.com/microcosm-cc/bluemonday); HTML user-input sanitization. [BSD-3-Clause License](https://spdx.org/licenses/BSD-3-Clause.html). | * [microcosm-cc/bluemonday](https://github.com/microcosm-cc/bluemonday); HTML user-input sanitization. [BSD-3-Clause License](https://spdx.org/licenses/BSD-3-Clause.html). | ||||||
| * [mvdan/xurls](https://github.com/mvdan/xurls); URL parsing regular expressions. [BSD-3-Clause License](https://spdx.org/licenses/BSD-3-Clause.html). | * [mvdan/xurls](https://github.com/mvdan/xurls); URL parsing regular expressions. [BSD-3-Clause License](https://spdx.org/licenses/BSD-3-Clause.html). | ||||||
| * [nfnt/resize](https://github.com/nfnt/resize); convenient image resizing. [ISC License](https://spdx.org/licenses/ISC.html). | * [nfnt/resize](https://github.com/nfnt/resize); convenient image resizing. [ISC License](https://spdx.org/licenses/ISC.html). | ||||||
|  | @ -148,9 +152,6 @@ The following libraries and frameworks are used by GoToSocial, with gratitude  | ||||||
| * [uptrace/bun](https://github.com/uptrace/bun); database ORM. [BSD-2-Clause License](https://spdx.org/licenses/BSD-2-Clause.html). | * [uptrace/bun](https://github.com/uptrace/bun); database ORM. [BSD-2-Clause License](https://spdx.org/licenses/BSD-2-Clause.html). | ||||||
| * [urfave/cli](https://github.com/urfave/cli); command-line interface framework. [MIT License](https://spdx.org/licenses/MIT.html). | * [urfave/cli](https://github.com/urfave/cli); command-line interface framework. [MIT License](https://spdx.org/licenses/MIT.html). | ||||||
| * [wagslane/go-password-validator](https://github.com/wagslane/go-password-validator); password strength validation. [MIT License](https://spdx.org/licenses/MIT.html). | * [wagslane/go-password-validator](https://github.com/wagslane/go-password-validator); password strength validation. [MIT License](https://spdx.org/licenses/MIT.html). | ||||||
| * [modernc.org/sqlite](sqlite); cgo-free port of SQLite. [Other License](https://gitlab.com/cznic/sqlite/-/blob/master/LICENSE). |  | ||||||
|   * [modernc.org/ccgo](ccgo); c99 AST -> Go translater. [BSD-3-Clause License](https://spdx.org/licenses/BSD-3-Clause.html). |  | ||||||
|   * [modernc.org/libc](libc); C-runtime services. [BSD-3-Clause License](https://spdx.org/licenses/BSD-3-Clause.html). |  | ||||||
| 
 | 
 | ||||||
| ### Image Attribution | ### Image Attribution | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										2
									
								
								go.mod
									
										
									
									
									
								
							
							
						
						
									
										2
									
								
								go.mod
									
										
									
									
									
								
							|  | @ -21,7 +21,7 @@ require ( | ||||||
| 	github.com/go-errors/errors v1.4.0 // indirect | 	github.com/go-errors/errors v1.4.0 // indirect | ||||||
| 	github.com/go-fed/activity v1.0.1-0.20210803212804-d866ba75dd0f | 	github.com/go-fed/activity v1.0.1-0.20210803212804-d866ba75dd0f | ||||||
| 	github.com/go-fed/httpsig v1.1.0 | 	github.com/go-fed/httpsig v1.1.0 | ||||||
| 	github.com/go-playground/validator/v10 v10.7.0 // indirect | 	github.com/go-playground/validator/v10 v10.7.0 | ||||||
| 	github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 // indirect | 	github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 // indirect | ||||||
| 	github.com/golang/mock v1.6.0 // indirect | 	github.com/golang/mock v1.6.0 // indirect | ||||||
| 	github.com/golang/protobuf v1.5.2 // indirect | 	github.com/golang/protobuf v1.5.2 // indirect | ||||||
|  |  | ||||||
							
								
								
									
										72
									
								
								internal/ap/activitystreams.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								internal/ap/activitystreams.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,72 @@ | ||||||
|  | /* | ||||||
|  |    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 ap | ||||||
|  | 
 | ||||||
|  | // https://www.w3.org/TR/activitystreams-vocabulary | ||||||
|  | const ( | ||||||
|  | 	ActivityAccept          = "Accept"          // ActivityStreamsAccept https://www.w3.org/TR/activitystreams-vocabulary/#dfn-accept | ||||||
|  | 	ActivityAdd             = "Add"             // ActivityStreamsAdd https://www.w3.org/TR/activitystreams-vocabulary/#dfn-add | ||||||
|  | 	ActivityAnnounce        = "Announce"        // ActivityStreamsAnnounce https://www.w3.org/TR/activitystreams-vocabulary/#dfn-announce | ||||||
|  | 	ActivityArrive          = "Arrive"          // ActivityStreamsArrive https://www.w3.org/TR/activitystreams-vocabulary/#dfn-arrive | ||||||
|  | 	ActivityBlock           = "Block"           // ActivityStreamsBlock https://www.w3.org/TR/activitystreams-vocabulary/#dfn-block | ||||||
|  | 	ActivityCreate          = "Create"          // ActivityStreamsCreate https://www.w3.org/TR/activitystreams-vocabulary/#dfn-create | ||||||
|  | 	ActivityDelete          = "Delete"          // ActivityStreamsDelete https://www.w3.org/TR/activitystreams-vocabulary/#dfn-delete | ||||||
|  | 	ActivityDislike         = "Dislike"         // ActivityStreamsDislike https://www.w3.org/TR/activitystreams-vocabulary/#dfn-dislike | ||||||
|  | 	ActivityFlag            = "Flag"            // ActivityStreamsFlag https://www.w3.org/TR/activitystreams-vocabulary/#dfn-flag | ||||||
|  | 	ActivityFollow          = "Follow"          // ActivityStreamsFollow https://www.w3.org/TR/activitystreams-vocabulary/#dfn-follow | ||||||
|  | 	ActivityIgnore          = "Ignore"          // ActivityStreamsIgnore https://www.w3.org/TR/activitystreams-vocabulary/#dfn-ignore | ||||||
|  | 	ActivityInvite          = "Invite"          // ActivityStreamsInvite https://www.w3.org/TR/activitystreams-vocabulary/#dfn-invite | ||||||
|  | 	ActivityJoin            = "Join"            // ActivityStreamsJoin https://www.w3.org/TR/activitystreams-vocabulary/#dfn-join | ||||||
|  | 	ActivityLeave           = "Leave"           // ActivityStreamsLeave https://www.w3.org/TR/activitystreams-vocabulary/#dfn-leave | ||||||
|  | 	ActivityLike            = "Like"            // ActivityStreamsLike https://www.w3.org/TR/activitystreams-vocabulary/#dfn-like | ||||||
|  | 	ActivityListen          = "Listen"          // ActivityStreamsListen https://www.w3.org/TR/activitystreams-vocabulary/#dfn-listen | ||||||
|  | 	ActivityMove            = "Move"            // ActivityStreamsMove https://www.w3.org/TR/activitystreams-vocabulary/#dfn-move | ||||||
|  | 	ActivityOffer           = "Offer"           // ActivityStreamsOffer https://www.w3.org/TR/activitystreams-vocabulary/#dfn-offer | ||||||
|  | 	ActivityQuestion        = "Question"        // ActivityStreamsQuestion https://www.w3.org/TR/activitystreams-vocabulary/#dfn-question | ||||||
|  | 	ActivityReject          = "Reject"          // ActivityStreamsReject https://www.w3.org/TR/activitystreams-vocabulary/#dfn-reject | ||||||
|  | 	ActivityRead            = "Read"            // ActivityStreamsRead https://www.w3.org/TR/activitystreams-vocabulary/#dfn-read | ||||||
|  | 	ActivityRemove          = "Remove"          // ActivityStreamsRemove https://www.w3.org/TR/activitystreams-vocabulary/#dfn-remove | ||||||
|  | 	ActivityTentativeReject = "TentativeReject" // ActivityStreamsTentativeReject https://www.w3.org/TR/activitystreams-vocabulary/#dfn-tentativereject | ||||||
|  | 	ActivityTentativeAccept = "TentativeAccept" // ActivityStreamsTentativeAccept https://www.w3.org/TR/activitystreams-vocabulary/#dfn-tentativeaccept | ||||||
|  | 	ActivityTravel          = "Travel"          // ActivityStreamsTravel https://www.w3.org/TR/activitystreams-vocabulary/#dfn-travel | ||||||
|  | 	ActivityUndo            = "Undo"            // ActivityStreamsUndo https://www.w3.org/TR/activitystreams-vocabulary/#dfn-undo | ||||||
|  | 	ActivityUpdate          = "Update"          // ActivityStreamsUpdate https://www.w3.org/TR/activitystreams-vocabulary/#dfn-update | ||||||
|  | 	ActivityView            = "View"            // ActivityStreamsView https://www.w3.org/TR/activitystreams-vocabulary/#dfn-view | ||||||
|  | 
 | ||||||
|  | 	ActorApplication  = "Application"  // ActivityStreamsApplication https://www.w3.org/TR/activitystreams-vocabulary/#dfn-application | ||||||
|  | 	ActorGroup        = "Group"        // ActivityStreamsGroup https://www.w3.org/TR/activitystreams-vocabulary/#dfn-group | ||||||
|  | 	ActorOrganization = "Organization" // ActivityStreamsOrganization https://www.w3.org/TR/activitystreams-vocabulary/#dfn-organization | ||||||
|  | 	ActorPerson       = "Person"       // ActivityStreamsPerson https://www.w3.org/TR/activitystreams-vocabulary/#dfn-person | ||||||
|  | 	ActorService      = "Service"      // ActivityStreamsService https://www.w3.org/TR/activitystreams-vocabulary/#dfn-service | ||||||
|  | 
 | ||||||
|  | 	ObjectArticle        = "Article"        // ActivityStreamsArticle https://www.w3.org/TR/activitystreams-vocabulary/#dfn-article | ||||||
|  | 	ObjectAudio          = "Audio"          // ActivityStreamsAudio https://www.w3.org/TR/activitystreams-vocabulary/#dfn-audio | ||||||
|  | 	ObjectDocument       = "Document"       // ActivityStreamsDocument https://www.w3.org/TR/activitystreams-vocabulary/#dfn-document | ||||||
|  | 	ObjectEvent          = "Event"          // ActivityStreamsEvent https://www.w3.org/TR/activitystreams-vocabulary/#dfn-event | ||||||
|  | 	ObjectImage          = "Image"          // ActivityStreamsImage https://www.w3.org/TR/activitystreams-vocabulary/#dfn-image | ||||||
|  | 	ObjectNote           = "Note"           // ActivityStreamsNote https://www.w3.org/TR/activitystreams-vocabulary/#dfn-note | ||||||
|  | 	ObjectPage           = "Page"           // ActivityStreamsPage https://www.w3.org/TR/activitystreams-vocabulary/#dfn-page | ||||||
|  | 	ObjectPlace          = "Place"          // ActivityStreamsPlace https://www.w3.org/TR/activitystreams-vocabulary/#dfn-place | ||||||
|  | 	ObjectProfile        = "Profile"        // ActivityStreamsProfile https://www.w3.org/TR/activitystreams-vocabulary/#dfn-profile | ||||||
|  | 	ObjectRelationship   = "Relationship"   // ActivityStreamsRelationship https://www.w3.org/TR/activitystreams-vocabulary/#dfn-relationship | ||||||
|  | 	ObjectTombstone      = "Tombstone"      // ActivityStreamsTombstone https://www.w3.org/TR/activitystreams-vocabulary/#dfn-tombstone | ||||||
|  | 	ObjectVideo          = "Video"          // ActivityStreamsVideo https://www.w3.org/TR/activitystreams-vocabulary/#dfn-video | ||||||
|  | 	ObjectCollection     = "Collection"     //ActivityStreamsCollection https://www.w3.org/TR/activitystreams-vocabulary/#dfn-collection | ||||||
|  | 	ObjectCollectionPage = "CollectionPage" // ActivityStreamsCollectionPage https://www.w3.org/TR/activitystreams-vocabulary/#dfn-collectionpage | ||||||
|  | ) | ||||||
|  | @ -9,7 +9,6 @@ import ( | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" | 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/federation" | 	"github.com/superseriousbusiness/gotosocial/internal/federation" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/oauth" |  | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/processing" | 	"github.com/superseriousbusiness/gotosocial/internal/processing" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/typeutils" | 	"github.com/superseriousbusiness/gotosocial/internal/typeutils" | ||||||
| ) | ) | ||||||
|  | @ -27,8 +26,8 @@ type AccountStandardTestSuite struct { | ||||||
| 	processor processing.Processor | 	processor processing.Processor | ||||||
| 
 | 
 | ||||||
| 	// standard suite models | 	// standard suite models | ||||||
| 	testTokens       map[string]*oauth.Token | 	testTokens       map[string]*gtsmodel.Token | ||||||
| 	testClients      map[string]*oauth.Client | 	testClients      map[string]*gtsmodel.Client | ||||||
| 	testApplications map[string]*gtsmodel.Application | 	testApplications map[string]*gtsmodel.Application | ||||||
| 	testUsers        map[string]*gtsmodel.User | 	testUsers        map[string]*gtsmodel.User | ||||||
| 	testAccounts     map[string]*gtsmodel.Account | 	testAccounts     map[string]*gtsmodel.Account | ||||||
|  |  | ||||||
|  | @ -27,7 +27,7 @@ import ( | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/api/model" | 	"github.com/superseriousbusiness/gotosocial/internal/api/model" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/config" | 	"github.com/superseriousbusiness/gotosocial/internal/config" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/oauth" | 	"github.com/superseriousbusiness/gotosocial/internal/oauth" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/util" | 	"github.com/superseriousbusiness/gotosocial/internal/validate" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // AccountCreatePOSTHandler swagger:operation POST /api/v1/accounts accountCreate | // AccountCreatePOSTHandler swagger:operation POST /api/v1/accounts accountCreate | ||||||
|  | @ -118,15 +118,15 @@ func validateCreateAccount(form *model.AccountCreateRequest, c *config.AccountsC | ||||||
| 		return errors.New("registration is not open for this server") | 		return errors.New("registration is not open for this server") | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if err := util.ValidateUsername(form.Username); err != nil { | 	if err := validate.Username(form.Username); err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if err := util.ValidateEmail(form.Email); err != nil { | 	if err := validate.Email(form.Email); err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if err := util.ValidateNewPassword(form.Password); err != nil { | 	if err := validate.NewPassword(form.Password); err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -134,11 +134,11 @@ func validateCreateAccount(form *model.AccountCreateRequest, c *config.AccountsC | ||||||
| 		return errors.New("agreement to terms and conditions not given") | 		return errors.New("agreement to terms and conditions not given") | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if err := util.ValidateLanguage(form.Locale); err != nil { | 	if err := validate.Language(form.Locale); err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if err := util.ValidateSignUpReason(form.Reason, c.ReasonRequired); err != nil { | 	if err := validate.SignUpReason(form.Reason, c.ReasonRequired); err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -28,7 +28,7 @@ import ( | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/api/model" | 	"github.com/superseriousbusiness/gotosocial/internal/api/model" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/media" | 	"github.com/superseriousbusiness/gotosocial/internal/media" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/oauth" | 	"github.com/superseriousbusiness/gotosocial/internal/oauth" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/util" | 	"github.com/superseriousbusiness/gotosocial/internal/validate" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // emojiCreateRequest swagger:operation POST /api/v1/admin/custom_emojis emojiCreate | // emojiCreateRequest swagger:operation POST /api/v1/admin/custom_emojis emojiCreate | ||||||
|  | @ -132,5 +132,5 @@ func validateCreateEmoji(form *model.EmojiCreateRequest) error { | ||||||
| 		return fmt.Errorf("file size limit exceeded: limit is %d bytes but emoji was %d bytes", media.EmojiMaxBytes, form.Image.Size) | 		return fmt.Errorf("file size limit exceeded: limit is %d bytes but emoji was %d bytes", media.EmojiMaxBytes, form.Image.Size) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return util.ValidateEmojiShortcode(form.Shortcode) | 	return validate.EmojiShortcode(form.Shortcode) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -41,7 +41,7 @@ type AuthTestSuite struct { | ||||||
| 	testAccount     *gtsmodel.Account | 	testAccount     *gtsmodel.Account | ||||||
| 	testApplication *gtsmodel.Application | 	testApplication *gtsmodel.Application | ||||||
| 	testUser        *gtsmodel.User | 	testUser        *gtsmodel.User | ||||||
| 	testClient      *oauth.Client | 	testClient      *gtsmodel.Client | ||||||
| 	config          *config.Config | 	config          *config.Config | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -83,7 +83,7 @@ func (suite *AuthTestSuite) SetupSuite() { | ||||||
| 		Email:             "user@example.org", | 		Email:             "user@example.org", | ||||||
| 		AccountID:         acctID, | 		AccountID:         acctID, | ||||||
| 	} | 	} | ||||||
| 	suite.testClient = &oauth.Client{ | 	suite.testClient = >smodel.Client{ | ||||||
| 		ID:     "a-known-client-id", | 		ID:     "a-known-client-id", | ||||||
| 		Secret: "some-secret", | 		Secret: "some-secret", | ||||||
| 		Domain: fmt.Sprintf("%s://%s", c.Protocol, c.Host), | 		Domain: fmt.Sprintf("%s://%s", c.Protocol, c.Host), | ||||||
|  | @ -95,7 +95,6 @@ func (suite *AuthTestSuite) SetupSuite() { | ||||||
| 		ClientID:     "a-known-client-id", | 		ClientID:     "a-known-client-id", | ||||||
| 		ClientSecret: "some-secret", | 		ClientSecret: "some-secret", | ||||||
| 		Scopes:       "read", | 		Scopes:       "read", | ||||||
| 		VapidKey:     uuid.NewString(), |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -112,8 +111,8 @@ func (suite *AuthTestSuite) SetupTest() { | ||||||
| 	suite.db = db | 	suite.db = db | ||||||
| 
 | 
 | ||||||
| 	models := []interface{}{ | 	models := []interface{}{ | ||||||
| 		&oauth.Client{}, | 		>smodel.Client{}, | ||||||
| 		&oauth.Token{}, | 		>smodel.Token{}, | ||||||
| 		>smodel.User{}, | 		>smodel.User{}, | ||||||
| 		>smodel.Account{}, | 		>smodel.Account{}, | ||||||
| 		>smodel.Application{}, | 		>smodel.Application{}, | ||||||
|  | @ -145,8 +144,8 @@ func (suite *AuthTestSuite) SetupTest() { | ||||||
| // TearDownTest drops the oauth_clients table and closes the pg connection after each test | // TearDownTest drops the oauth_clients table and closes the pg connection after each test | ||||||
| func (suite *AuthTestSuite) TearDownTest() { | func (suite *AuthTestSuite) TearDownTest() { | ||||||
| 	models := []interface{}{ | 	models := []interface{}{ | ||||||
| 		&oauth.Client{}, | 		>smodel.Client{}, | ||||||
| 		&oauth.Token{}, | 		>smodel.Token{}, | ||||||
| 		>smodel.User{}, | 		>smodel.User{}, | ||||||
| 		>smodel.Account{}, | 		>smodel.Account{}, | ||||||
| 		>smodel.Application{}, | 		>smodel.Application{}, | ||||||
|  |  | ||||||
|  | @ -33,7 +33,7 @@ import ( | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" | 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/oidc" | 	"github.com/superseriousbusiness/gotosocial/internal/oidc" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/util" | 	"github.com/superseriousbusiness/gotosocial/internal/validate" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // CallbackGETHandler parses a token from an external auth provider. | // CallbackGETHandler parses a token from an external auth provider. | ||||||
|  | @ -153,7 +153,7 @@ func (m *Module) parseUserFromClaims(ctx context.Context, claims *oidc.Claims, i | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// check if we can just use claims.Name as-is | 	// check if we can just use claims.Name as-is | ||||||
| 	err = util.ValidateUsername(claims.Name) | 	err = validate.Username(claims.Name) | ||||||
| 	if err == nil { | 	if err == nil { | ||||||
| 		// the name we have on the claims is already a valid username | 		// the name we have on the claims is already a valid username | ||||||
| 		username = claims.Name | 		username = claims.Name | ||||||
|  | @ -166,7 +166,7 @@ func (m *Module) parseUserFromClaims(ctx context.Context, claims *oidc.Claims, i | ||||||
| 		// lowercase the whole thing | 		// lowercase the whole thing | ||||||
| 		lower := strings.ToLower(underscored) | 		lower := strings.ToLower(underscored) | ||||||
| 		// see if this is valid.... | 		// see if this is valid.... | ||||||
| 		if err := util.ValidateUsername(lower); err == nil { | 		if err := validate.Username(lower); err == nil { | ||||||
| 			// we managed to get a valid username | 			// we managed to get a valid username | ||||||
| 			username = lower | 			username = lower | ||||||
| 		} else { | 		} else { | ||||||
|  |  | ||||||
|  | @ -57,8 +57,8 @@ type ServeFileTestSuite struct { | ||||||
| 	oauthServer  oauth.Server | 	oauthServer  oauth.Server | ||||||
| 
 | 
 | ||||||
| 	// standard suite models | 	// standard suite models | ||||||
| 	testTokens       map[string]*oauth.Token | 	testTokens       map[string]*gtsmodel.Token | ||||||
| 	testClients      map[string]*oauth.Client | 	testClients      map[string]*gtsmodel.Client | ||||||
| 	testApplications map[string]*gtsmodel.Application | 	testApplications map[string]*gtsmodel.Application | ||||||
| 	testUsers        map[string]*gtsmodel.User | 	testUsers        map[string]*gtsmodel.User | ||||||
| 	testAccounts     map[string]*gtsmodel.Account | 	testAccounts     map[string]*gtsmodel.Account | ||||||
|  |  | ||||||
|  | @ -60,8 +60,8 @@ type MediaCreateTestSuite struct { | ||||||
| 	processor    processing.Processor | 	processor    processing.Processor | ||||||
| 
 | 
 | ||||||
| 	// standard suite models | 	// standard suite models | ||||||
| 	testTokens       map[string]*oauth.Token | 	testTokens       map[string]*gtsmodel.Token | ||||||
| 	testClients      map[string]*oauth.Client | 	testClients      map[string]*gtsmodel.Client | ||||||
| 	testApplications map[string]*gtsmodel.Application | 	testApplications map[string]*gtsmodel.Application | ||||||
| 	testUsers        map[string]*gtsmodel.User | 	testUsers        map[string]*gtsmodel.User | ||||||
| 	testAccounts     map[string]*gtsmodel.Account | 	testAccounts     map[string]*gtsmodel.Account | ||||||
|  |  | ||||||
|  | @ -27,7 +27,6 @@ import ( | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" | 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/federation" | 	"github.com/superseriousbusiness/gotosocial/internal/federation" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/oauth" |  | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/processing" | 	"github.com/superseriousbusiness/gotosocial/internal/processing" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/typeutils" | 	"github.com/superseriousbusiness/gotosocial/internal/typeutils" | ||||||
| ) | ) | ||||||
|  | @ -45,8 +44,8 @@ type StatusStandardTestSuite struct { | ||||||
| 	storage   blob.Storage | 	storage   blob.Storage | ||||||
| 
 | 
 | ||||||
| 	// standard suite models | 	// standard suite models | ||||||
| 	testTokens       map[string]*oauth.Token | 	testTokens       map[string]*gtsmodel.Token | ||||||
| 	testClients      map[string]*oauth.Client | 	testClients      map[string]*gtsmodel.Client | ||||||
| 	testApplications map[string]*gtsmodel.Application | 	testApplications map[string]*gtsmodel.Application | ||||||
| 	testUsers        map[string]*gtsmodel.User | 	testUsers        map[string]*gtsmodel.User | ||||||
| 	testAccounts     map[string]*gtsmodel.Account | 	testAccounts     map[string]*gtsmodel.Account | ||||||
|  |  | ||||||
|  | @ -27,7 +27,7 @@ import ( | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/api/model" | 	"github.com/superseriousbusiness/gotosocial/internal/api/model" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/config" | 	"github.com/superseriousbusiness/gotosocial/internal/config" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/oauth" | 	"github.com/superseriousbusiness/gotosocial/internal/oauth" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/util" | 	"github.com/superseriousbusiness/gotosocial/internal/validate" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // StatusCreatePOSTHandler swagger:operation POST /api/v1/statuses statusCreate | // StatusCreatePOSTHandler swagger:operation POST /api/v1/statuses statusCreate | ||||||
|  | @ -157,7 +157,7 @@ func validateCreateStatus(form *model.AdvancedStatusCreateForm, config *config.S | ||||||
| 
 | 
 | ||||||
| 	// validate post language | 	// validate post language | ||||||
| 	if form.Language != "" { | 	if form.Language != "" { | ||||||
| 		if err := util.ValidateLanguage(form.Language); err != nil { | 		if err := validate.Language(form.Language); err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -146,13 +146,13 @@ func (m *Module) StreamGETHandler(c *gin.Context) { | ||||||
| 	} | 	} | ||||||
| 	defer conn.Close() // whatever happens, when we leave this function we want to close the websocket connection | 	defer conn.Close() // whatever happens, when we leave this function we want to close the websocket connection | ||||||
| 
 | 
 | ||||||
| 	// inform the processor that we have a new connection and want a stream for it | 	// inform the processor that we have a new connection and want a s for it | ||||||
| 	stream, errWithCode := m.processor.OpenStreamForAccount(c.Request.Context(), account, streamType) | 	s, errWithCode := m.processor.OpenStreamForAccount(c.Request.Context(), account, streamType) | ||||||
| 	if errWithCode != nil { | 	if errWithCode != nil { | ||||||
| 		c.JSON(errWithCode.Code(), errWithCode.Safe()) | 		c.JSON(errWithCode.Code(), errWithCode.Safe()) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	defer close(stream.Hangup) // closing stream.Hangup indicates that we've finished with the connection (the client has gone), so we want to do this on exiting this handler | 	defer close(s.Hangup) // closing stream.Hangup indicates that we've finished with the connection (the client has gone), so we want to do this on exiting this handler | ||||||
| 
 | 
 | ||||||
| 	// spawn a new ticker for pinging the connection periodically | 	// spawn a new ticker for pinging the connection periodically | ||||||
| 	t := time.NewTicker(30 * time.Second) | 	t := time.NewTicker(30 * time.Second) | ||||||
|  | @ -161,7 +161,7 @@ func (m *Module) StreamGETHandler(c *gin.Context) { | ||||||
| sendLoop: | sendLoop: | ||||||
| 	for { | 	for { | ||||||
| 		select { | 		select { | ||||||
| 		case m := <-stream.Messages: | 		case m := <-s.Messages: | ||||||
| 			// we've got a streaming message!! | 			// we've got a streaming message!! | ||||||
| 			l.Trace("received message from stream") | 			l.Trace("received message from stream") | ||||||
| 			if err := conn.WriteJSON(m); err != nil { | 			if err := conn.WriteJSON(m); err != nil { | ||||||
|  |  | ||||||
|  | @ -10,7 +10,6 @@ import ( | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" | 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/federation" | 	"github.com/superseriousbusiness/gotosocial/internal/federation" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/oauth" |  | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/processing" | 	"github.com/superseriousbusiness/gotosocial/internal/processing" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/typeutils" | 	"github.com/superseriousbusiness/gotosocial/internal/typeutils" | ||||||
| ) | ) | ||||||
|  | @ -29,8 +28,8 @@ type UserStandardTestSuite struct { | ||||||
| 	securityModule *security.Module | 	securityModule *security.Module | ||||||
| 
 | 
 | ||||||
| 	// standard suite models | 	// standard suite models | ||||||
| 	testTokens       map[string]*oauth.Token | 	testTokens       map[string]*gtsmodel.Token | ||||||
| 	testClients      map[string]*oauth.Client | 	testClients      map[string]*gtsmodel.Client | ||||||
| 	testApplications map[string]*gtsmodel.Application | 	testApplications map[string]*gtsmodel.Application | ||||||
| 	testUsers        map[string]*gtsmodel.User | 	testUsers        map[string]*gtsmodel.User | ||||||
| 	testAccounts     map[string]*gtsmodel.Account | 	testAccounts     map[string]*gtsmodel.Account | ||||||
|  |  | ||||||
|  | @ -30,7 +30,7 @@ import ( | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" | 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db/bundb" | 	"github.com/superseriousbusiness/gotosocial/internal/db/bundb" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/util" | 	"github.com/superseriousbusiness/gotosocial/internal/validate" | ||||||
| 	"golang.org/x/crypto/bcrypt" | 	"golang.org/x/crypto/bcrypt" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | @ -45,7 +45,7 @@ var Create cliactions.GTSAction = func(ctx context.Context, c *config.Config, lo | ||||||
| 	if !ok { | 	if !ok { | ||||||
| 		return errors.New("no username set") | 		return errors.New("no username set") | ||||||
| 	} | 	} | ||||||
| 	if err := util.ValidateUsername(username); err != nil { | 	if err := validate.Username(username); err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -53,7 +53,7 @@ var Create cliactions.GTSAction = func(ctx context.Context, c *config.Config, lo | ||||||
| 	if !ok { | 	if !ok { | ||||||
| 		return errors.New("no email set") | 		return errors.New("no email set") | ||||||
| 	} | 	} | ||||||
| 	if err := util.ValidateEmail(email); err != nil { | 	if err := validate.Email(email); err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -61,7 +61,7 @@ var Create cliactions.GTSAction = func(ctx context.Context, c *config.Config, lo | ||||||
| 	if !ok { | 	if !ok { | ||||||
| 		return errors.New("no password set") | 		return errors.New("no password set") | ||||||
| 	} | 	} | ||||||
| 	if err := util.ValidateNewPassword(password); err != nil { | 	if err := validate.NewPassword(password); err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -84,7 +84,7 @@ var Confirm cliactions.GTSAction = func(ctx context.Context, c *config.Config, l | ||||||
| 	if !ok { | 	if !ok { | ||||||
| 		return errors.New("no username set") | 		return errors.New("no username set") | ||||||
| 	} | 	} | ||||||
| 	if err := util.ValidateUsername(username); err != nil { | 	if err := validate.Username(username); err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -119,7 +119,7 @@ var Promote cliactions.GTSAction = func(ctx context.Context, c *config.Config, l | ||||||
| 	if !ok { | 	if !ok { | ||||||
| 		return errors.New("no username set") | 		return errors.New("no username set") | ||||||
| 	} | 	} | ||||||
| 	if err := util.ValidateUsername(username); err != nil { | 	if err := validate.Username(username); err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -151,7 +151,7 @@ var Demote cliactions.GTSAction = func(ctx context.Context, c *config.Config, lo | ||||||
| 	if !ok { | 	if !ok { | ||||||
| 		return errors.New("no username set") | 		return errors.New("no username set") | ||||||
| 	} | 	} | ||||||
| 	if err := util.ValidateUsername(username); err != nil { | 	if err := validate.Username(username); err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -183,7 +183,7 @@ var Disable cliactions.GTSAction = func(ctx context.Context, c *config.Config, l | ||||||
| 	if !ok { | 	if !ok { | ||||||
| 		return errors.New("no username set") | 		return errors.New("no username set") | ||||||
| 	} | 	} | ||||||
| 	if err := util.ValidateUsername(username); err != nil { | 	if err := validate.Username(username); err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -221,7 +221,7 @@ var Password cliactions.GTSAction = func(ctx context.Context, c *config.Config, | ||||||
| 	if !ok { | 	if !ok { | ||||||
| 		return errors.New("no username set") | 		return errors.New("no username set") | ||||||
| 	} | 	} | ||||||
| 	if err := util.ValidateUsername(username); err != nil { | 	if err := validate.Username(username); err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -229,7 +229,7 @@ var Password cliactions.GTSAction = func(ctx context.Context, c *config.Config, | ||||||
| 	if !ok { | 	if !ok { | ||||||
| 		return errors.New("no password set") | 		return errors.New("no password set") | ||||||
| 	} | 	} | ||||||
| 	if err := util.ValidateNewPassword(password); err != nil { | 	if err := validate.NewPassword(password); err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -73,8 +73,8 @@ var models []interface{} = []interface{}{ | ||||||
| 	>smodel.Instance{}, | 	>smodel.Instance{}, | ||||||
| 	>smodel.Notification{}, | 	>smodel.Notification{}, | ||||||
| 	>smodel.RouterSession{}, | 	>smodel.RouterSession{}, | ||||||
| 	&oauth.Token{}, | 	>smodel.Token{}, | ||||||
| 	&oauth.Client{}, | 	>smodel.Client{}, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Start creates and starts a gotosocial server | // Start creates and starts a gotosocial server | ||||||
|  |  | ||||||
|  | @ -29,6 +29,7 @@ import ( | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/ap" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/config" | 	"github.com/superseriousbusiness/gotosocial/internal/config" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" | 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
|  | @ -113,7 +114,7 @@ func (a *adminDB) NewSignup(ctx context.Context, username string, reason string, | ||||||
| 			PrivateKey:            key, | 			PrivateKey:            key, | ||||||
| 			PublicKey:             &key.PublicKey, | 			PublicKey:             &key.PublicKey, | ||||||
| 			PublicKeyURI:          newAccountURIs.PublicKeyURI, | 			PublicKeyURI:          newAccountURIs.PublicKeyURI, | ||||||
| 			ActorType:             gtsmodel.ActivityStreamsPerson, | 			ActorType:             ap.ActorPerson, | ||||||
| 			URI:                   newAccountURIs.UserURI, | 			URI:                   newAccountURIs.UserURI, | ||||||
| 			InboxURI:              newAccountURIs.InboxURI, | 			InboxURI:              newAccountURIs.InboxURI, | ||||||
| 			OutboxURI:             newAccountURIs.OutboxURI, | 			OutboxURI:             newAccountURIs.OutboxURI, | ||||||
|  | @ -207,7 +208,7 @@ func (a *adminDB) CreateInstanceAccount(ctx context.Context) db.Error { | ||||||
| 		PrivateKey:            key, | 		PrivateKey:            key, | ||||||
| 		PublicKey:             &key.PublicKey, | 		PublicKey:             &key.PublicKey, | ||||||
| 		PublicKeyURI:          newAccountURIs.PublicKeyURI, | 		PublicKeyURI:          newAccountURIs.PublicKeyURI, | ||||||
| 		ActorType:             gtsmodel.ActivityStreamsPerson, | 		ActorType:             ap.ActorPerson, | ||||||
| 		URI:                   newAccountURIs.UserURI, | 		URI:                   newAccountURIs.UserURI, | ||||||
| 		InboxURI:              newAccountURIs.InboxURI, | 		InboxURI:              newAccountURIs.InboxURI, | ||||||
| 		OutboxURI:             newAccountURIs.OutboxURI, | 		OutboxURI:             newAccountURIs.OutboxURI, | ||||||
|  |  | ||||||
|  | @ -37,11 +37,13 @@ import ( | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/cache" | 	"github.com/superseriousbusiness/gotosocial/internal/cache" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/config" | 	"github.com/superseriousbusiness/gotosocial/internal/config" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" | 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/db/bundb/migrations" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/id" | 	"github.com/superseriousbusiness/gotosocial/internal/id" | ||||||
| 	"github.com/uptrace/bun" | 	"github.com/uptrace/bun" | ||||||
| 	"github.com/uptrace/bun/dialect/pgdialect" | 	"github.com/uptrace/bun/dialect/pgdialect" | ||||||
| 	"github.com/uptrace/bun/dialect/sqlitedialect" | 	"github.com/uptrace/bun/dialect/sqlitedialect" | ||||||
|  | 	"github.com/uptrace/bun/migrate" | ||||||
| 	_ "modernc.org/sqlite" | 	_ "modernc.org/sqlite" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | @ -73,6 +75,32 @@ type bunDBService struct { | ||||||
| 	conn   *DBConn | 	conn   *DBConn | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func doMigration(ctx context.Context, db *bun.DB, log *logrus.Logger) error { | ||||||
|  | 	l := log.WithField("func", "doMigration") | ||||||
|  | 
 | ||||||
|  | 	migrator := migrate.NewMigrator(db, migrations.Migrations) | ||||||
|  | 
 | ||||||
|  | 	if err := migrator.Init(ctx); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	group, err := migrator.Migrate(ctx) | ||||||
|  | 	if err != nil { | ||||||
|  | 		if err.Error() == "migrate: there are no any migrations" { | ||||||
|  | 			return nil | ||||||
|  | 		} | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if group.ID == 0 { | ||||||
|  | 		l.Info("there are no new migrations to run") | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	l.Infof("MIGRATED DATABASE TO %s", group) | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // NewBunDBService returns a bunDB derived from the provided config, which implements the go-fed DB interface. | // NewBunDBService returns a bunDB derived from the provided config, which implements the go-fed DB interface. | ||||||
| // Under the hood, it uses https://github.com/uptrace/bun to create and maintain a database connection. | // Under the hood, it uses https://github.com/uptrace/bun to create and maintain a database connection. | ||||||
| func NewBunDBService(ctx context.Context, c *config.Config, log *logrus.Logger) (db.DB, error) { | func NewBunDBService(ctx context.Context, c *config.Config, log *logrus.Logger) (db.DB, error) { | ||||||
|  | @ -130,6 +158,10 @@ func NewBunDBService(ctx context.Context, c *config.Config, log *logrus.Logger) | ||||||
| 		conn.RegisterModel(t) | 		conn.RegisterModel(t) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	if err := doMigration(ctx, conn.DB, log); err != nil { | ||||||
|  | 		return nil, fmt.Errorf("db migration error: %s", err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	accounts := &accountDB{config: c, conn: conn, cache: cache.NewAccountCache()} | 	accounts := &accountDB{config: c, conn: conn, cache: cache.NewAccountCache()} | ||||||
| 
 | 
 | ||||||
| 	ps := &bunDBService{ | 	ps := &bunDBService{ | ||||||
|  |  | ||||||
|  | @ -24,7 +24,6 @@ import ( | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/config" | 	"github.com/superseriousbusiness/gotosocial/internal/config" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" | 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/oauth" |  | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type BunDBStandardTestSuite struct { | type BunDBStandardTestSuite struct { | ||||||
|  | @ -35,8 +34,8 @@ type BunDBStandardTestSuite struct { | ||||||
| 	log    *logrus.Logger | 	log    *logrus.Logger | ||||||
| 
 | 
 | ||||||
| 	// standard suite models | 	// standard suite models | ||||||
| 	testTokens       map[string]*oauth.Token | 	testTokens       map[string]*gtsmodel.Token | ||||||
| 	testClients      map[string]*oauth.Client | 	testClients      map[string]*gtsmodel.Client | ||||||
| 	testApplications map[string]*gtsmodel.Application | 	testApplications map[string]*gtsmodel.Application | ||||||
| 	testUsers        map[string]*gtsmodel.User | 	testUsers        map[string]*gtsmodel.User | ||||||
| 	testAccounts     map[string]*gtsmodel.Account | 	testAccounts     map[string]*gtsmodel.Account | ||||||
|  |  | ||||||
							
								
								
									
										70
									
								
								internal/db/bundb/migrations/README.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								internal/db/bundb/migrations/README.md
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,70 @@ | ||||||
|  | # Migrations | ||||||
|  | 
 | ||||||
|  | ## How do I write a migration file? | ||||||
|  | 
 | ||||||
|  | [See here](https://bun.uptrace.dev/guide/migrations.html#migration-names) | ||||||
|  | 
 | ||||||
|  | As a template, take one of the existing migration files and modify it, or use the below code snippet: | ||||||
|  | 
 | ||||||
|  | ```go | ||||||
|  | /* | ||||||
|  |    GoToSocial | ||||||
|  |    Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org | ||||||
|  | 
 | ||||||
|  |    This program is free software: you can redistribute it and/or modify | ||||||
|  |    it under the terms of the GNU Affero General Public License as published by | ||||||
|  |    the Free Software Foundation, either version 3 of the License, or | ||||||
|  |    (at your option) any later version. | ||||||
|  | 
 | ||||||
|  |    This program is distributed in the hope that it will be useful, | ||||||
|  |    but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  |    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  |    GNU Affero General Public License for more details. | ||||||
|  | 
 | ||||||
|  |    You should have received a copy of the GNU Affero General Public License | ||||||
|  |    along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  | */ | ||||||
|  | 
 | ||||||
|  | package migrations | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 
 | ||||||
|  | 	"github.com/uptrace/bun" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func init() { | ||||||
|  | 	up := func(ctx context.Context, db *bun.DB) error { | ||||||
|  | 		return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error { | ||||||
|  | 			// your logic here | ||||||
|  |             return nil | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	down := func(ctx context.Context, db *bun.DB) error { | ||||||
|  | 		return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error { | ||||||
|  | 			// your logic here | ||||||
|  |             return nil | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if err := Migrations.Register(up, down); err != nil { | ||||||
|  | 		panic(err) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ## File format | ||||||
|  | 
 | ||||||
|  | Bun requires a very specific format: 14 digits, then letters or underscores. | ||||||
|  | 
 | ||||||
|  | You can use the following bash command on your branch to generate a suitable migration filename. | ||||||
|  | 
 | ||||||
|  | ```bash | ||||||
|  | echo "$(date --utc +%Y%m%H%M%S%N | head -c 14)_$(git rev-parse --abbrev-ref HEAD).go" | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ## Rules of thumb | ||||||
|  | 
 | ||||||
|  | 1. **DON'T DROP TABLES**!!!!!!!! | ||||||
|  | 2. Don't make something `NOT NULL` if it's likely to already contain `null` fields. | ||||||
|  | @ -16,4 +16,13 @@ | ||||||
|    along with this program.  If not, see <http://www.gnu.org/licenses/>. |    along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
| */ | */ | ||||||
| 
 | 
 | ||||||
| package gtsmodel | package migrations | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"github.com/uptrace/bun/migrate" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | var ( | ||||||
|  | 	// Migrations provides migration logic for bun | ||||||
|  | 	Migrations = migrate.NewMigrations() | ||||||
|  | ) | ||||||
|  | @ -165,19 +165,19 @@ func (d *deref) dereferenceAccountable(ctx context.Context, username string, rem | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	switch t.GetTypeName() { | 	switch t.GetTypeName() { | ||||||
| 	case string(gtsmodel.ActivityStreamsPerson): | 	case ap.ActorPerson: | ||||||
| 		p, ok := t.(vocab.ActivityStreamsPerson) | 		p, ok := t.(vocab.ActivityStreamsPerson) | ||||||
| 		if !ok { | 		if !ok { | ||||||
| 			return nil, errors.New("DereferenceAccountable: error resolving type as activitystreams person") | 			return nil, errors.New("DereferenceAccountable: error resolving type as activitystreams person") | ||||||
| 		} | 		} | ||||||
| 		return p, nil | 		return p, nil | ||||||
| 	case string(gtsmodel.ActivityStreamsApplication): | 	case ap.ActorApplication: | ||||||
| 		p, ok := t.(vocab.ActivityStreamsApplication) | 		p, ok := t.(vocab.ActivityStreamsApplication) | ||||||
| 		if !ok { | 		if !ok { | ||||||
| 			return nil, errors.New("DereferenceAccountable: error resolving type as activitystreams application") | 			return nil, errors.New("DereferenceAccountable: error resolving type as activitystreams application") | ||||||
| 		} | 		} | ||||||
| 		return p, nil | 		return p, nil | ||||||
| 	case string(gtsmodel.ActivityStreamsService): | 	case ap.ActorService: | ||||||
| 		p, ok := t.(vocab.ActivityStreamsService) | 		p, ok := t.(vocab.ActivityStreamsService) | ||||||
| 		if !ok { | 		if !ok { | ||||||
| 			return nil, errors.New("DereferenceAccountable: error resolving type as activitystreams service") | 			return nil, errors.New("DereferenceAccountable: error resolving type as activitystreams service") | ||||||
|  |  | ||||||
|  | @ -28,7 +28,6 @@ import ( | ||||||
| 	"github.com/go-fed/activity/streams" | 	"github.com/go-fed/activity/streams" | ||||||
| 	"github.com/go-fed/activity/streams/vocab" | 	"github.com/go-fed/activity/streams/vocab" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/ap" | 	"github.com/superseriousbusiness/gotosocial/internal/ap" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" |  | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // DereferenceCollectionPage returns the activitystreams CollectionPage at the specified IRI, or an error if something goes wrong. | // DereferenceCollectionPage returns the activitystreams CollectionPage at the specified IRI, or an error if something goes wrong. | ||||||
|  | @ -57,7 +56,7 @@ func (d *deref) DereferenceCollectionPage(ctx context.Context, username string, | ||||||
| 		return nil, fmt.Errorf("DereferenceCollectionPage: error resolving json into ap vocab type: %s", err) | 		return nil, fmt.Errorf("DereferenceCollectionPage: error resolving json into ap vocab type: %s", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if t.GetTypeName() != gtsmodel.ActivityStreamsCollectionPage { | 	if t.GetTypeName() != ap.ObjectCollectionPage { | ||||||
| 		return nil, fmt.Errorf("DereferenceCollectionPage: type name %s not supported", t.GetTypeName()) | 		return nil, fmt.Errorf("DereferenceCollectionPage: type name %s not supported", t.GetTypeName()) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -154,55 +154,55 @@ func (d *deref) dereferenceStatusable(ctx context.Context, username string, remo | ||||||
| 
 | 
 | ||||||
| 	// Article, Document, Image, Video, Note, Page, Event, Place, Mention, Profile | 	// Article, Document, Image, Video, Note, Page, Event, Place, Mention, Profile | ||||||
| 	switch t.GetTypeName() { | 	switch t.GetTypeName() { | ||||||
| 	case gtsmodel.ActivityStreamsArticle: | 	case ap.ObjectArticle: | ||||||
| 		p, ok := t.(vocab.ActivityStreamsArticle) | 		p, ok := t.(vocab.ActivityStreamsArticle) | ||||||
| 		if !ok { | 		if !ok { | ||||||
| 			return nil, errors.New("DereferenceStatusable: error resolving type as ActivityStreamsArticle") | 			return nil, errors.New("DereferenceStatusable: error resolving type as ActivityStreamsArticle") | ||||||
| 		} | 		} | ||||||
| 		return p, nil | 		return p, nil | ||||||
| 	case gtsmodel.ActivityStreamsDocument: | 	case ap.ObjectDocument: | ||||||
| 		p, ok := t.(vocab.ActivityStreamsDocument) | 		p, ok := t.(vocab.ActivityStreamsDocument) | ||||||
| 		if !ok { | 		if !ok { | ||||||
| 			return nil, errors.New("DereferenceStatusable: error resolving type as ActivityStreamsDocument") | 			return nil, errors.New("DereferenceStatusable: error resolving type as ActivityStreamsDocument") | ||||||
| 		} | 		} | ||||||
| 		return p, nil | 		return p, nil | ||||||
| 	case gtsmodel.ActivityStreamsImage: | 	case ap.ObjectImage: | ||||||
| 		p, ok := t.(vocab.ActivityStreamsImage) | 		p, ok := t.(vocab.ActivityStreamsImage) | ||||||
| 		if !ok { | 		if !ok { | ||||||
| 			return nil, errors.New("DereferenceStatusable: error resolving type as ActivityStreamsImage") | 			return nil, errors.New("DereferenceStatusable: error resolving type as ActivityStreamsImage") | ||||||
| 		} | 		} | ||||||
| 		return p, nil | 		return p, nil | ||||||
| 	case gtsmodel.ActivityStreamsVideo: | 	case ap.ObjectVideo: | ||||||
| 		p, ok := t.(vocab.ActivityStreamsVideo) | 		p, ok := t.(vocab.ActivityStreamsVideo) | ||||||
| 		if !ok { | 		if !ok { | ||||||
| 			return nil, errors.New("DereferenceStatusable: error resolving type as ActivityStreamsVideo") | 			return nil, errors.New("DereferenceStatusable: error resolving type as ActivityStreamsVideo") | ||||||
| 		} | 		} | ||||||
| 		return p, nil | 		return p, nil | ||||||
| 	case gtsmodel.ActivityStreamsNote: | 	case ap.ObjectNote: | ||||||
| 		p, ok := t.(vocab.ActivityStreamsNote) | 		p, ok := t.(vocab.ActivityStreamsNote) | ||||||
| 		if !ok { | 		if !ok { | ||||||
| 			return nil, errors.New("DereferenceStatusable: error resolving type as ActivityStreamsNote") | 			return nil, errors.New("DereferenceStatusable: error resolving type as ActivityStreamsNote") | ||||||
| 		} | 		} | ||||||
| 		return p, nil | 		return p, nil | ||||||
| 	case gtsmodel.ActivityStreamsPage: | 	case ap.ObjectPage: | ||||||
| 		p, ok := t.(vocab.ActivityStreamsPage) | 		p, ok := t.(vocab.ActivityStreamsPage) | ||||||
| 		if !ok { | 		if !ok { | ||||||
| 			return nil, errors.New("DereferenceStatusable: error resolving type as ActivityStreamsPage") | 			return nil, errors.New("DereferenceStatusable: error resolving type as ActivityStreamsPage") | ||||||
| 		} | 		} | ||||||
| 		return p, nil | 		return p, nil | ||||||
| 	case gtsmodel.ActivityStreamsEvent: | 	case ap.ObjectEvent: | ||||||
| 		p, ok := t.(vocab.ActivityStreamsEvent) | 		p, ok := t.(vocab.ActivityStreamsEvent) | ||||||
| 		if !ok { | 		if !ok { | ||||||
| 			return nil, errors.New("DereferenceStatusable: error resolving type as ActivityStreamsEvent") | 			return nil, errors.New("DereferenceStatusable: error resolving type as ActivityStreamsEvent") | ||||||
| 		} | 		} | ||||||
| 		return p, nil | 		return p, nil | ||||||
| 	case gtsmodel.ActivityStreamsPlace: | 	case ap.ObjectPlace: | ||||||
| 		p, ok := t.(vocab.ActivityStreamsPlace) | 		p, ok := t.(vocab.ActivityStreamsPlace) | ||||||
| 		if !ok { | 		if !ok { | ||||||
| 			return nil, errors.New("DereferenceStatusable: error resolving type as ActivityStreamsPlace") | 			return nil, errors.New("DereferenceStatusable: error resolving type as ActivityStreamsPlace") | ||||||
| 		} | 		} | ||||||
| 		return p, nil | 		return p, nil | ||||||
| 	case gtsmodel.ActivityStreamsProfile: | 	case ap.ObjectProfile: | ||||||
| 		p, ok := t.(vocab.ActivityStreamsProfile) | 		p, ok := t.(vocab.ActivityStreamsProfile) | ||||||
| 		if !ok { | 		if !ok { | ||||||
| 			return nil, errors.New("DereferenceStatusable: error resolving type as ActivityStreamsProfile") | 			return nil, errors.New("DereferenceStatusable: error resolving type as ActivityStreamsProfile") | ||||||
|  |  | ||||||
|  | @ -29,6 +29,7 @@ import ( | ||||||
| 
 | 
 | ||||||
| 	"github.com/go-fed/activity/streams" | 	"github.com/go-fed/activity/streams" | ||||||
| 	"github.com/stretchr/testify/suite" | 	"github.com/stretchr/testify/suite" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/ap" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" | 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/federation/dereferencing" | 	"github.com/superseriousbusiness/gotosocial/internal/federation/dereferencing" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
|  | @ -133,7 +134,7 @@ func (suite *StatusTestSuite) TestDereferenceSimpleStatus() { | ||||||
| 	suite.False(status.Local) | 	suite.False(status.Local) | ||||||
| 	suite.Empty(status.ContentWarning) | 	suite.Empty(status.ContentWarning) | ||||||
| 	suite.Equal(gtsmodel.VisibilityPublic, status.Visibility) | 	suite.Equal(gtsmodel.VisibilityPublic, status.Visibility) | ||||||
| 	suite.Equal(gtsmodel.ActivityStreamsNote, status.ActivityStreamsType) | 	suite.Equal(ap.ObjectNote, status.ActivityStreamsType) | ||||||
| 
 | 
 | ||||||
| 	// status should be in the database | 	// status should be in the database | ||||||
| 	dbStatus, err := suite.db.GetStatusByURI(context.Background(), status.URI) | 	dbStatus, err := suite.db.GetStatusByURI(context.Background(), status.URI) | ||||||
|  | @ -171,7 +172,7 @@ func (suite *StatusTestSuite) TestDereferenceStatusWithMention() { | ||||||
| 	suite.False(status.Local) | 	suite.False(status.Local) | ||||||
| 	suite.Empty(status.ContentWarning) | 	suite.Empty(status.ContentWarning) | ||||||
| 	suite.Equal(gtsmodel.VisibilityPublic, status.Visibility) | 	suite.Equal(gtsmodel.VisibilityPublic, status.Visibility) | ||||||
| 	suite.Equal(gtsmodel.ActivityStreamsNote, status.ActivityStreamsType) | 	suite.Equal(ap.ObjectNote, status.ActivityStreamsType) | ||||||
| 
 | 
 | ||||||
| 	// status should be in the database | 	// status should be in the database | ||||||
| 	dbStatus, err := suite.db.GetStatusByURI(context.Background(), status.URI) | 	dbStatus, err := suite.db.GetStatusByURI(context.Background(), status.URI) | ||||||
|  |  | ||||||
|  | @ -27,8 +27,10 @@ import ( | ||||||
| 	"github.com/go-fed/activity/streams" | 	"github.com/go-fed/activity/streams" | ||||||
| 	"github.com/go-fed/activity/streams/vocab" | 	"github.com/go-fed/activity/streams/vocab" | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/ap" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" | 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/messages" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/util" | 	"github.com/superseriousbusiness/gotosocial/internal/util" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | @ -67,7 +69,7 @@ func (f *federatingDB) Accept(ctx context.Context, accept vocab.ActivityStreamsA | ||||||
| 		l.Error("ACCEPT: from federator channel wasn't set on context") | 		l.Error("ACCEPT: from federator channel wasn't set on context") | ||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
| 	fromFederatorChan, ok := fromFederatorChanI.(chan gtsmodel.FromFederator) | 	fromFederatorChan, ok := fromFederatorChanI.(chan messages.FromFederator) | ||||||
| 	if !ok { | 	if !ok { | ||||||
| 		l.Error("ACCEPT: from federator channel was set on context but couldn't be parsed") | 		l.Error("ACCEPT: from federator channel was set on context but couldn't be parsed") | ||||||
| 		return nil | 		return nil | ||||||
|  | @ -99,9 +101,9 @@ func (f *federatingDB) Accept(ctx context.Context, accept vocab.ActivityStreamsA | ||||||
| 					return err | 					return err | ||||||
| 				} | 				} | ||||||
| 
 | 
 | ||||||
| 				fromFederatorChan <- gtsmodel.FromFederator{ | 				fromFederatorChan <- messages.FromFederator{ | ||||||
| 					APObjectType:     gtsmodel.ActivityStreamsFollow, | 					APObjectType:     ap.ActivityFollow, | ||||||
| 					APActivityType:   gtsmodel.ActivityStreamsAccept, | 					APActivityType:   ap.ActivityAccept, | ||||||
| 					GTSModel:         follow, | 					GTSModel:         follow, | ||||||
| 					ReceivingAccount: targetAcct, | 					ReceivingAccount: targetAcct, | ||||||
| 				} | 				} | ||||||
|  | @ -116,7 +118,7 @@ func (f *federatingDB) Accept(ctx context.Context, accept vocab.ActivityStreamsA | ||||||
| 		} | 		} | ||||||
| 		switch iter.GetType().GetTypeName() { | 		switch iter.GetType().GetTypeName() { | ||||||
| 		// we have the whole object so we can figure out what we're accepting | 		// we have the whole object so we can figure out what we're accepting | ||||||
| 		case string(gtsmodel.ActivityStreamsFollow): | 		case ap.ActivityFollow: | ||||||
| 			// ACCEPT FOLLOW | 			// ACCEPT FOLLOW | ||||||
| 			asFollow, ok := iter.GetType().(vocab.ActivityStreamsFollow) | 			asFollow, ok := iter.GetType().(vocab.ActivityStreamsFollow) | ||||||
| 			if !ok { | 			if !ok { | ||||||
|  | @ -136,9 +138,9 @@ func (f *federatingDB) Accept(ctx context.Context, accept vocab.ActivityStreamsA | ||||||
| 				return err | 				return err | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			fromFederatorChan <- gtsmodel.FromFederator{ | 			fromFederatorChan <- messages.FromFederator{ | ||||||
| 				APObjectType:     gtsmodel.ActivityStreamsFollow, | 				APObjectType:     ap.ActivityFollow, | ||||||
| 				APActivityType:   gtsmodel.ActivityStreamsAccept, | 				APActivityType:   ap.ActivityAccept, | ||||||
| 				GTSModel:         follow, | 				GTSModel:         follow, | ||||||
| 				ReceivingAccount: targetAcct, | 				ReceivingAccount: targetAcct, | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
|  | @ -26,7 +26,9 @@ import ( | ||||||
| 	"github.com/go-fed/activity/streams" | 	"github.com/go-fed/activity/streams" | ||||||
| 	"github.com/go-fed/activity/streams/vocab" | 	"github.com/go-fed/activity/streams/vocab" | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/ap" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/messages" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/util" | 	"github.com/superseriousbusiness/gotosocial/internal/util" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | @ -65,7 +67,7 @@ func (f *federatingDB) Announce(ctx context.Context, announce vocab.ActivityStre | ||||||
| 		l.Error("ANNOUNCE: from federator channel wasn't set on context") | 		l.Error("ANNOUNCE: from federator channel wasn't set on context") | ||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
| 	fromFederatorChan, ok := fromFederatorChanI.(chan gtsmodel.FromFederator) | 	fromFederatorChan, ok := fromFederatorChanI.(chan messages.FromFederator) | ||||||
| 	if !ok { | 	if !ok { | ||||||
| 		l.Error("ANNOUNCE: from federator channel was set on context but couldn't be parsed") | 		l.Error("ANNOUNCE: from federator channel was set on context but couldn't be parsed") | ||||||
| 		return nil | 		return nil | ||||||
|  | @ -82,9 +84,9 @@ func (f *federatingDB) Announce(ctx context.Context, announce vocab.ActivityStre | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// it's a new announce so pass it back to the processor async for dereferencing etc | 	// it's a new announce so pass it back to the processor async for dereferencing etc | ||||||
| 	fromFederatorChan <- gtsmodel.FromFederator{ | 	fromFederatorChan <- messages.FromFederator{ | ||||||
| 		APObjectType:     gtsmodel.ActivityStreamsAnnounce, | 		APObjectType:     ap.ActivityAnnounce, | ||||||
| 		APActivityType:   gtsmodel.ActivityStreamsCreate, | 		APActivityType:   ap.ActivityCreate, | ||||||
| 		GTSModel:         boost, | 		GTSModel:         boost, | ||||||
| 		ReceivingAccount: targetAcct, | 		ReceivingAccount: targetAcct, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -27,9 +27,11 @@ import ( | ||||||
| 	"github.com/go-fed/activity/streams" | 	"github.com/go-fed/activity/streams" | ||||||
| 	"github.com/go-fed/activity/streams/vocab" | 	"github.com/go-fed/activity/streams/vocab" | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/ap" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" | 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/id" | 	"github.com/superseriousbusiness/gotosocial/internal/id" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/messages" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/util" | 	"github.com/superseriousbusiness/gotosocial/internal/util" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | @ -81,14 +83,14 @@ func (f *federatingDB) Create(ctx context.Context, asType vocab.Type) error { | ||||||
| 		l.Error("CREATE: from federator channel wasn't set on context") | 		l.Error("CREATE: from federator channel wasn't set on context") | ||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
| 	fromFederatorChan, ok := fromFederatorChanI.(chan gtsmodel.FromFederator) | 	fromFederatorChan, ok := fromFederatorChanI.(chan messages.FromFederator) | ||||||
| 	if !ok { | 	if !ok { | ||||||
| 		l.Error("CREATE: from federator channel was set on context but couldn't be parsed") | 		l.Error("CREATE: from federator channel was set on context but couldn't be parsed") | ||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	switch asType.GetTypeName() { | 	switch asType.GetTypeName() { | ||||||
| 	case gtsmodel.ActivityStreamsCreate: | 	case ap.ActivityCreate: | ||||||
| 		// CREATE SOMETHING | 		// CREATE SOMETHING | ||||||
| 		create, ok := asType.(vocab.ActivityStreamsCreate) | 		create, ok := asType.(vocab.ActivityStreamsCreate) | ||||||
| 		if !ok { | 		if !ok { | ||||||
|  | @ -97,7 +99,7 @@ func (f *federatingDB) Create(ctx context.Context, asType vocab.Type) error { | ||||||
| 		object := create.GetActivityStreamsObject() | 		object := create.GetActivityStreamsObject() | ||||||
| 		for objectIter := object.Begin(); objectIter != object.End(); objectIter = objectIter.Next() { | 		for objectIter := object.Begin(); objectIter != object.End(); objectIter = objectIter.Next() { | ||||||
| 			switch objectIter.GetType().GetTypeName() { | 			switch objectIter.GetType().GetTypeName() { | ||||||
| 			case gtsmodel.ActivityStreamsNote: | 			case ap.ObjectNote: | ||||||
| 				// CREATE A NOTE | 				// CREATE A NOTE | ||||||
| 				note := objectIter.GetActivityStreamsNote() | 				note := objectIter.GetActivityStreamsNote() | ||||||
| 				status, err := f.typeConverter.ASStatusToStatus(ctx, note) | 				status, err := f.typeConverter.ASStatusToStatus(ctx, note) | ||||||
|  | @ -122,15 +124,15 @@ func (f *federatingDB) Create(ctx context.Context, asType vocab.Type) error { | ||||||
| 					return fmt.Errorf("CREATE: database error inserting status: %s", err) | 					return fmt.Errorf("CREATE: database error inserting status: %s", err) | ||||||
| 				} | 				} | ||||||
| 
 | 
 | ||||||
| 				fromFederatorChan <- gtsmodel.FromFederator{ | 				fromFederatorChan <- messages.FromFederator{ | ||||||
| 					APObjectType:     gtsmodel.ActivityStreamsNote, | 					APObjectType:     ap.ObjectNote, | ||||||
| 					APActivityType:   gtsmodel.ActivityStreamsCreate, | 					APActivityType:   ap.ActivityCreate, | ||||||
| 					GTSModel:         status, | 					GTSModel:         status, | ||||||
| 					ReceivingAccount: targetAcct, | 					ReceivingAccount: targetAcct, | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	case gtsmodel.ActivityStreamsFollow: | 	case ap.ActivityFollow: | ||||||
| 		// FOLLOW SOMETHING | 		// FOLLOW SOMETHING | ||||||
| 		follow, ok := asType.(vocab.ActivityStreamsFollow) | 		follow, ok := asType.(vocab.ActivityStreamsFollow) | ||||||
| 		if !ok { | 		if !ok { | ||||||
|  | @ -152,13 +154,13 @@ func (f *federatingDB) Create(ctx context.Context, asType vocab.Type) error { | ||||||
| 			return fmt.Errorf("CREATE: database error inserting follow request: %s", err) | 			return fmt.Errorf("CREATE: database error inserting follow request: %s", err) | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		fromFederatorChan <- gtsmodel.FromFederator{ | 		fromFederatorChan <- messages.FromFederator{ | ||||||
| 			APObjectType:     gtsmodel.ActivityStreamsFollow, | 			APObjectType:     ap.ActivityFollow, | ||||||
| 			APActivityType:   gtsmodel.ActivityStreamsCreate, | 			APActivityType:   ap.ActivityCreate, | ||||||
| 			GTSModel:         followRequest, | 			GTSModel:         followRequest, | ||||||
| 			ReceivingAccount: targetAcct, | 			ReceivingAccount: targetAcct, | ||||||
| 		} | 		} | ||||||
| 	case gtsmodel.ActivityStreamsLike: | 	case ap.ActivityLike: | ||||||
| 		// LIKE SOMETHING | 		// LIKE SOMETHING | ||||||
| 		like, ok := asType.(vocab.ActivityStreamsLike) | 		like, ok := asType.(vocab.ActivityStreamsLike) | ||||||
| 		if !ok { | 		if !ok { | ||||||
|  | @ -180,13 +182,13 @@ func (f *federatingDB) Create(ctx context.Context, asType vocab.Type) error { | ||||||
| 			return fmt.Errorf("CREATE: database error inserting fave: %s", err) | 			return fmt.Errorf("CREATE: database error inserting fave: %s", err) | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		fromFederatorChan <- gtsmodel.FromFederator{ | 		fromFederatorChan <- messages.FromFederator{ | ||||||
| 			APObjectType:     gtsmodel.ActivityStreamsLike, | 			APObjectType:     ap.ActivityLike, | ||||||
| 			APActivityType:   gtsmodel.ActivityStreamsCreate, | 			APActivityType:   ap.ActivityCreate, | ||||||
| 			GTSModel:         fave, | 			GTSModel:         fave, | ||||||
| 			ReceivingAccount: targetAcct, | 			ReceivingAccount: targetAcct, | ||||||
| 		} | 		} | ||||||
| 	case gtsmodel.ActivityStreamsBlock: | 	case ap.ActivityBlock: | ||||||
| 		// BLOCK SOMETHING | 		// BLOCK SOMETHING | ||||||
| 		blockable, ok := asType.(vocab.ActivityStreamsBlock) | 		blockable, ok := asType.(vocab.ActivityStreamsBlock) | ||||||
| 		if !ok { | 		if !ok { | ||||||
|  | @ -208,9 +210,9 @@ func (f *federatingDB) Create(ctx context.Context, asType vocab.Type) error { | ||||||
| 			return fmt.Errorf("CREATE: database error inserting block: %s", err) | 			return fmt.Errorf("CREATE: database error inserting block: %s", err) | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		fromFederatorChan <- gtsmodel.FromFederator{ | 		fromFederatorChan <- messages.FromFederator{ | ||||||
| 			APObjectType:     gtsmodel.ActivityStreamsBlock, | 			APObjectType:     ap.ActivityBlock, | ||||||
| 			APActivityType:   gtsmodel.ActivityStreamsCreate, | 			APActivityType:   ap.ActivityCreate, | ||||||
| 			GTSModel:         block, | 			GTSModel:         block, | ||||||
| 			ReceivingAccount: targetAcct, | 			ReceivingAccount: targetAcct, | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | @ -24,7 +24,9 @@ import ( | ||||||
| 	"net/url" | 	"net/url" | ||||||
| 
 | 
 | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/ap" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/messages" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/util" | 	"github.com/superseriousbusiness/gotosocial/internal/util" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | @ -61,7 +63,7 @@ func (f *federatingDB) Delete(ctx context.Context, id *url.URL) error { | ||||||
| 		l.Error("DELETE: from federator channel wasn't set on context") | 		l.Error("DELETE: from federator channel wasn't set on context") | ||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
| 	fromFederatorChan, ok := fromFederatorChanI.(chan gtsmodel.FromFederator) | 	fromFederatorChan, ok := fromFederatorChanI.(chan messages.FromFederator) | ||||||
| 	if !ok { | 	if !ok { | ||||||
| 		l.Error("DELETE: from federator channel was set on context but couldn't be parsed") | 		l.Error("DELETE: from federator channel was set on context but couldn't be parsed") | ||||||
| 		return nil | 		return nil | ||||||
|  | @ -76,9 +78,9 @@ func (f *federatingDB) Delete(ctx context.Context, id *url.URL) error { | ||||||
| 		if err := f.db.DeleteByID(ctx, s.ID, >smodel.Status{}); err != nil { | 		if err := f.db.DeleteByID(ctx, s.ID, >smodel.Status{}); err != nil { | ||||||
| 			return fmt.Errorf("DELETE: err deleting status: %s", err) | 			return fmt.Errorf("DELETE: err deleting status: %s", err) | ||||||
| 		} | 		} | ||||||
| 		fromFederatorChan <- gtsmodel.FromFederator{ | 		fromFederatorChan <- messages.FromFederator{ | ||||||
| 			APObjectType:     gtsmodel.ActivityStreamsNote, | 			APObjectType:     ap.ObjectNote, | ||||||
| 			APActivityType:   gtsmodel.ActivityStreamsDelete, | 			APActivityType:   ap.ActivityDelete, | ||||||
| 			GTSModel:         s, | 			GTSModel:         s, | ||||||
| 			ReceivingAccount: targetAcct, | 			ReceivingAccount: targetAcct, | ||||||
| 		} | 		} | ||||||
|  | @ -91,9 +93,9 @@ func (f *federatingDB) Delete(ctx context.Context, id *url.URL) error { | ||||||
| 		if err := f.db.DeleteByID(ctx, a.ID, >smodel.Account{}); err != nil { | 		if err := f.db.DeleteByID(ctx, a.ID, >smodel.Account{}); err != nil { | ||||||
| 			return fmt.Errorf("DELETE: err deleting account: %s", err) | 			return fmt.Errorf("DELETE: err deleting account: %s", err) | ||||||
| 		} | 		} | ||||||
| 		fromFederatorChan <- gtsmodel.FromFederator{ | 		fromFederatorChan <- messages.FromFederator{ | ||||||
| 			APObjectType:     gtsmodel.ActivityStreamsProfile, | 			APObjectType:     ap.ObjectProfile, | ||||||
| 			APActivityType:   gtsmodel.ActivityStreamsDelete, | 			APActivityType:   ap.ActivityDelete, | ||||||
| 			GTSModel:         a, | 			GTSModel:         a, | ||||||
| 			ReceivingAccount: targetAcct, | 			ReceivingAccount: targetAcct, | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | @ -27,6 +27,7 @@ import ( | ||||||
| 	"github.com/go-fed/activity/streams" | 	"github.com/go-fed/activity/streams" | ||||||
| 	"github.com/go-fed/activity/streams/vocab" | 	"github.com/go-fed/activity/streams/vocab" | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/ap" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" | 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/util" | 	"github.com/superseriousbusiness/gotosocial/internal/util" | ||||||
|  | @ -72,7 +73,7 @@ func (f *federatingDB) Undo(ctx context.Context, undo vocab.ActivityStreamsUndo) | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 		switch iter.GetType().GetTypeName() { | 		switch iter.GetType().GetTypeName() { | ||||||
| 		case string(gtsmodel.ActivityStreamsFollow): | 		case ap.ActivityFollow: | ||||||
| 			// UNDO FOLLOW | 			// UNDO FOLLOW | ||||||
| 			ASFollow, ok := iter.GetType().(vocab.ActivityStreamsFollow) | 			ASFollow, ok := iter.GetType().(vocab.ActivityStreamsFollow) | ||||||
| 			if !ok { | 			if !ok { | ||||||
|  | @ -101,11 +102,11 @@ func (f *federatingDB) Undo(ctx context.Context, undo vocab.ActivityStreamsUndo) | ||||||
| 			} | 			} | ||||||
| 			l.Debug("follow undone") | 			l.Debug("follow undone") | ||||||
| 			return nil | 			return nil | ||||||
| 		case string(gtsmodel.ActivityStreamsLike): | 		case ap.ActivityLike: | ||||||
| 			// UNDO LIKE | 			// UNDO LIKE | ||||||
| 		case string(gtsmodel.ActivityStreamsAnnounce): | 		case ap.ActivityAnnounce: | ||||||
| 			// UNDO BOOST/REBLOG/ANNOUNCE | 			// UNDO BOOST/REBLOG/ANNOUNCE | ||||||
| 		case string(gtsmodel.ActivityStreamsBlock): | 		case ap.ActivityBlock: | ||||||
| 			// UNDO BLOCK | 			// UNDO BLOCK | ||||||
| 			ASBlock, ok := iter.GetType().(vocab.ActivityStreamsBlock) | 			ASBlock, ok := iter.GetType().(vocab.ActivityStreamsBlock) | ||||||
| 			if !ok { | 			if !ok { | ||||||
|  |  | ||||||
|  | @ -29,6 +29,7 @@ import ( | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/ap" | 	"github.com/superseriousbusiness/gotosocial/internal/ap" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/messages" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/util" | 	"github.com/superseriousbusiness/gotosocial/internal/util" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | @ -84,50 +85,50 @@ func (f *federatingDB) Update(ctx context.Context, asType vocab.Type) error { | ||||||
| 	if fromFederatorChanI == nil { | 	if fromFederatorChanI == nil { | ||||||
| 		l.Error("UPDATE: from federator channel wasn't set on context") | 		l.Error("UPDATE: from federator channel wasn't set on context") | ||||||
| 	} | 	} | ||||||
| 	fromFederatorChan, ok := fromFederatorChanI.(chan gtsmodel.FromFederator) | 	fromFederatorChan, ok := fromFederatorChanI.(chan messages.FromFederator) | ||||||
| 	if !ok { | 	if !ok { | ||||||
| 		l.Error("UPDATE: from federator channel was set on context but couldn't be parsed") | 		l.Error("UPDATE: from federator channel was set on context but couldn't be parsed") | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	typeName := asType.GetTypeName() | 	typeName := asType.GetTypeName() | ||||||
| 	if typeName == gtsmodel.ActivityStreamsApplication || | 	if typeName == ap.ActorApplication || | ||||||
| 		typeName == gtsmodel.ActivityStreamsGroup || | 		typeName == ap.ActorGroup || | ||||||
| 		typeName == gtsmodel.ActivityStreamsOrganization || | 		typeName == ap.ActorOrganization || | ||||||
| 		typeName == gtsmodel.ActivityStreamsPerson || | 		typeName == ap.ActorPerson || | ||||||
| 		typeName == gtsmodel.ActivityStreamsService { | 		typeName == ap.ActorService { | ||||||
| 		// it's an UPDATE to some kind of account | 		// it's an UPDATE to some kind of account | ||||||
| 		var accountable ap.Accountable | 		var accountable ap.Accountable | ||||||
| 
 | 
 | ||||||
| 		switch asType.GetTypeName() { | 		switch asType.GetTypeName() { | ||||||
| 		case gtsmodel.ActivityStreamsApplication: | 		case ap.ActorApplication: | ||||||
| 			l.Debug("got update for APPLICATION") | 			l.Debug("got update for APPLICATION") | ||||||
| 			i, ok := asType.(vocab.ActivityStreamsApplication) | 			i, ok := asType.(vocab.ActivityStreamsApplication) | ||||||
| 			if !ok { | 			if !ok { | ||||||
| 				return errors.New("UPDATE: could not convert type to application") | 				return errors.New("UPDATE: could not convert type to application") | ||||||
| 			} | 			} | ||||||
| 			accountable = i | 			accountable = i | ||||||
| 		case gtsmodel.ActivityStreamsGroup: | 		case ap.ActorGroup: | ||||||
| 			l.Debug("got update for GROUP") | 			l.Debug("got update for GROUP") | ||||||
| 			i, ok := asType.(vocab.ActivityStreamsGroup) | 			i, ok := asType.(vocab.ActivityStreamsGroup) | ||||||
| 			if !ok { | 			if !ok { | ||||||
| 				return errors.New("UPDATE: could not convert type to group") | 				return errors.New("UPDATE: could not convert type to group") | ||||||
| 			} | 			} | ||||||
| 			accountable = i | 			accountable = i | ||||||
| 		case gtsmodel.ActivityStreamsOrganization: | 		case ap.ActorOrganization: | ||||||
| 			l.Debug("got update for ORGANIZATION") | 			l.Debug("got update for ORGANIZATION") | ||||||
| 			i, ok := asType.(vocab.ActivityStreamsOrganization) | 			i, ok := asType.(vocab.ActivityStreamsOrganization) | ||||||
| 			if !ok { | 			if !ok { | ||||||
| 				return errors.New("UPDATE: could not convert type to organization") | 				return errors.New("UPDATE: could not convert type to organization") | ||||||
| 			} | 			} | ||||||
| 			accountable = i | 			accountable = i | ||||||
| 		case gtsmodel.ActivityStreamsPerson: | 		case ap.ActorPerson: | ||||||
| 			l.Debug("got update for PERSON") | 			l.Debug("got update for PERSON") | ||||||
| 			i, ok := asType.(vocab.ActivityStreamsPerson) | 			i, ok := asType.(vocab.ActivityStreamsPerson) | ||||||
| 			if !ok { | 			if !ok { | ||||||
| 				return errors.New("UPDATE: could not convert type to person") | 				return errors.New("UPDATE: could not convert type to person") | ||||||
| 			} | 			} | ||||||
| 			accountable = i | 			accountable = i | ||||||
| 		case gtsmodel.ActivityStreamsService: | 		case ap.ActorService: | ||||||
| 			l.Debug("got update for SERVICE") | 			l.Debug("got update for SERVICE") | ||||||
| 			i, ok := asType.(vocab.ActivityStreamsService) | 			i, ok := asType.(vocab.ActivityStreamsService) | ||||||
| 			if !ok { | 			if !ok { | ||||||
|  | @ -157,9 +158,9 @@ func (f *federatingDB) Update(ctx context.Context, asType vocab.Type) error { | ||||||
| 			return fmt.Errorf("UPDATE: database error inserting updated account: %s", err) | 			return fmt.Errorf("UPDATE: database error inserting updated account: %s", err) | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		fromFederatorChan <- gtsmodel.FromFederator{ | 		fromFederatorChan <- messages.FromFederator{ | ||||||
| 			APObjectType:     gtsmodel.ActivityStreamsProfile, | 			APObjectType:     ap.ObjectProfile, | ||||||
| 			APActivityType:   gtsmodel.ActivityStreamsUpdate, | 			APActivityType:   ap.ActivityUpdate, | ||||||
| 			GTSModel:         updatedAcct, | 			GTSModel:         updatedAcct, | ||||||
| 			ReceivingAccount: targetAcct, | 			ReceivingAccount: targetAcct, | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | @ -28,6 +28,7 @@ import ( | ||||||
| 	"github.com/go-fed/activity/streams" | 	"github.com/go-fed/activity/streams" | ||||||
| 	"github.com/go-fed/activity/streams/vocab" | 	"github.com/go-fed/activity/streams/vocab" | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/ap" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" | 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/id" | 	"github.com/superseriousbusiness/gotosocial/internal/id" | ||||||
|  | @ -78,7 +79,7 @@ func (f *federatingDB) NewID(ctx context.Context, t vocab.Type) (idURL *url.URL, | ||||||
| 	l.Debugf("received NEWID request for asType %s", string(b)) | 	l.Debugf("received NEWID request for asType %s", string(b)) | ||||||
| 
 | 
 | ||||||
| 	switch t.GetTypeName() { | 	switch t.GetTypeName() { | ||||||
| 	case gtsmodel.ActivityStreamsFollow: | 	case ap.ActivityFollow: | ||||||
| 		// FOLLOW | 		// FOLLOW | ||||||
| 		// ID might already be set on a follow we've created, so check it here and return it if it is | 		// ID might already be set on a follow we've created, so check it here and return it if it is | ||||||
| 		follow, ok := t.(vocab.ActivityStreamsFollow) | 		follow, ok := t.(vocab.ActivityStreamsFollow) | ||||||
|  | @ -108,7 +109,7 @@ func (f *federatingDB) NewID(ctx context.Context, t vocab.Type) (idURL *url.URL, | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	case gtsmodel.ActivityStreamsNote: | 	case ap.ObjectNote: | ||||||
| 		// NOTE aka STATUS | 		// NOTE aka STATUS | ||||||
| 		// ID might already be set on a note we've created, so check it here and return it if it is | 		// ID might already be set on a note we've created, so check it here and return it if it is | ||||||
| 		note, ok := t.(vocab.ActivityStreamsNote) | 		note, ok := t.(vocab.ActivityStreamsNote) | ||||||
|  | @ -121,7 +122,7 @@ func (f *federatingDB) NewID(ctx context.Context, t vocab.Type) (idURL *url.URL, | ||||||
| 				return idProp.GetIRI(), nil | 				return idProp.GetIRI(), nil | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	case gtsmodel.ActivityStreamsLike: | 	case ap.ActivityLike: | ||||||
| 		// LIKE aka FAVE | 		// LIKE aka FAVE | ||||||
| 		// ID might already be set on a fave we've created, so check it here and return it if it is | 		// ID might already be set on a fave we've created, so check it here and return it if it is | ||||||
| 		fave, ok := t.(vocab.ActivityStreamsLike) | 		fave, ok := t.(vocab.ActivityStreamsLike) | ||||||
|  | @ -134,7 +135,7 @@ func (f *federatingDB) NewID(ctx context.Context, t vocab.Type) (idURL *url.URL, | ||||||
| 				return idProp.GetIRI(), nil | 				return idProp.GetIRI(), nil | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	case gtsmodel.ActivityStreamsAnnounce: | 	case ap.ActivityAnnounce: | ||||||
| 		// ANNOUNCE aka BOOST | 		// ANNOUNCE aka BOOST | ||||||
| 		// ID might already be set on an announce we've created, so check it here and return it if it is | 		// ID might already be set on an announce we've created, so check it here and return it if it is | ||||||
| 		announce, ok := t.(vocab.ActivityStreamsAnnounce) | 		announce, ok := t.(vocab.ActivityStreamsAnnounce) | ||||||
|  | @ -147,7 +148,7 @@ func (f *federatingDB) NewID(ctx context.Context, t vocab.Type) (idURL *url.URL, | ||||||
| 				return idProp.GetIRI(), nil | 				return idProp.GetIRI(), nil | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	case gtsmodel.ActivityStreamsUpdate: | 	case ap.ActivityUpdate: | ||||||
| 		// UPDATE | 		// UPDATE | ||||||
| 		// ID might already be set on an update we've created, so check it here and return it if it is | 		// ID might already be set on an update we've created, so check it here and return it if it is | ||||||
| 		update, ok := t.(vocab.ActivityStreamsUpdate) | 		update, ok := t.(vocab.ActivityStreamsUpdate) | ||||||
|  | @ -160,7 +161,7 @@ func (f *federatingDB) NewID(ctx context.Context, t vocab.Type) (idURL *url.URL, | ||||||
| 				return idProp.GetIRI(), nil | 				return idProp.GetIRI(), nil | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	case gtsmodel.ActivityStreamsBlock: | 	case ap.ActivityBlock: | ||||||
| 		// BLOCK | 		// BLOCK | ||||||
| 		// ID might already be set on a block we've created, so check it here and return it if it is | 		// ID might already be set on a block we've created, so check it here and return it if it is | ||||||
| 		block, ok := t.(vocab.ActivityStreamsBlock) | 		block, ok := t.(vocab.ActivityStreamsBlock) | ||||||
|  | @ -173,7 +174,7 @@ func (f *federatingDB) NewID(ctx context.Context, t vocab.Type) (idURL *url.URL, | ||||||
| 				return idProp.GetIRI(), nil | 				return idProp.GetIRI(), nil | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	case gtsmodel.ActivityStreamsUndo: | 	case ap.ActivityUndo: | ||||||
| 		// UNDO | 		// UNDO | ||||||
| 		// ID might already be set on an undo we've created, so check it here and return it if it is | 		// ID might already be set on an undo we've created, so check it here and return it if it is | ||||||
| 		undo, ok := t.(vocab.ActivityStreamsUndo) | 		undo, ok := t.(vocab.ActivityStreamsUndo) | ||||||
|  |  | ||||||
|  | @ -27,124 +27,73 @@ import ( | ||||||
| 	"time" | 	"time" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // Account represents either a local or a remote fediverse account, gotosocial or otherwise (mastodon, pleroma, etc) | // Account represents either a local or a remote fediverse account, gotosocial or otherwise (mastodon, pleroma, etc). | ||||||
| type Account struct { | type Account struct { | ||||||
| 	/* | 	ID                      string           `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"`                                               // id of this item in the database | ||||||
| 		BASIC INFO | 	CreatedAt               time.Time        `validate:"-" bun:"type:timestamp,nullzero,notnull,default:current_timestamp"`                                          // when was item created | ||||||
| 	*/ | 	UpdatedAt               time.Time        `validate:"-" bun:"type:timestamp,nullzero,notnull,default:current_timestamp"`                                          // when was item last updated | ||||||
| 
 | 	Username                string           `validate:"required" bun:",nullzero,notnull,unique:userdomain"`                                                         // Username of the account, should just be a string of [a-zA-Z0-9_]. Can be added to domain to create the full username in the form ``[username]@[domain]`` eg., ``user_96@example.org``. Username and domain should be unique *with* each other | ||||||
| 	// id of this account in the local database | 	Domain                  string           `validate:"omitempty,fqdn" bun:",nullzero,unique:userdomain"`                                                           // Domain of the account, will be null if this is a local account, otherwise something like ``example.org`` or ``mastodon.social``. Should be unique with username. | ||||||
| 	ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` | 	AvatarMediaAttachmentID string           `validate:"omitempty,ulid" bun:"type:CHAR(26),nullzero"`                                                                // Database ID of the media attachment, if present | ||||||
| 	// Username of the account, should just be a string of [a-z0-9_]. Can be added to domain to create the full username in the form ``[username]@[domain]`` eg., ``user_96@example.org`` | 	AvatarMediaAttachment   *MediaAttachment `validate:"-" bun:"rel:belongs-to"`                                                                                     // MediaAttachment corresponding to avatarMediaAttachmentID | ||||||
| 	Username string `bun:",notnull,unique:userdomain,nullzero"` // username and domain should be unique *with* each other | 	AvatarRemoteURL         string           `validate:"omitempty,url" bun:",nullzero"`                                                                              // For a non-local account, where can the header be fetched? | ||||||
| 	// Domain of the account, will be null if this is a local account, otherwise something like ``example.org`` or ``mastodon.social``. Should be unique with username. | 	HeaderMediaAttachmentID string           `validate:"omitempty,ulid" bun:"type:CHAR(26),nullzero"`                                                                // Database ID of the media attachment, if present | ||||||
| 	Domain string `bun:",unique:userdomain,nullzero"` // username and domain should be unique *with* each other | 	HeaderMediaAttachment   *MediaAttachment `validate:"-" bun:"rel:belongs-to"`                                                                                     // MediaAttachment corresponding to headerMediaAttachmentID | ||||||
| 
 | 	HeaderRemoteURL         string           `validate:"omitempty,url" bun:",nullzero"`                                                                              // For a non-local account, where can the header be fetched? | ||||||
| 	/* | 	DisplayName             string           `validate:"-" bun:",nullzero"`                                                                                          // DisplayName for this account. Can be empty, then just the Username will be used for display purposes. | ||||||
| 		ACCOUNT METADATA | 	Fields                  []Field          `validate:"-"`                                                                                                          // a key/value map of fields that this account has added to their profile | ||||||
| 	*/ | 	Note                    string           `validate:"-" bun:",nullzero"`                                                                                          // A note that this account has on their profile (ie., the account's bio/description of themselves) | ||||||
| 
 | 	Memorial                bool             `validate:"-" bun:",nullzero,default:false"`                                                                            // Is this a memorial account, ie., has the user passed away? | ||||||
| 	// ID of the avatar as a media attachment | 	AlsoKnownAs             string           `validate:"omitempty,ulid" bun:"type:CHAR(26),nullzero"`                                                                // This account is associated with x account id | ||||||
| 	AvatarMediaAttachmentID string           `bun:"type:CHAR(26),nullzero"` | 	MovedToAccountID        string           `validate:"omitempty,ulid" bun:"type:CHAR(26),nullzero"`                                                                // This account has moved this account id in the database | ||||||
| 	AvatarMediaAttachment   *MediaAttachment `bun:"rel:belongs-to"` | 	Bot                     bool             `validate:"-" bun:",nullzero,default:false"`                                                                            // Does this account identify itself as a bot? | ||||||
| 	// For a non-local account, where can the header be fetched? | 	Reason                  string           `validate:"-" bun:",nullzero"`                                                                                          // What reason was given for signing up when this account was created? | ||||||
| 	AvatarRemoteURL string `bun:",nullzero"` | 	Locked                  bool             `validate:"-" bun:",nullzero,default:true"`                                                                             // Does this account need an approval for new followers? | ||||||
| 	// ID of the header as a media attachment | 	Discoverable            bool             `validate:"-" bun:",nullzero,default:false"`                                                                            // Should this account be shown in the instance's profile directory? | ||||||
| 	HeaderMediaAttachmentID string           `bun:"type:CHAR(26),nullzero"` | 	Privacy                 Visibility       `validate:"required_without=Domain,omitempty,oneof=public unlocked followers_only mutuals_only direct" bun:",nullzero"` // Default post privacy for this account | ||||||
| 	HeaderMediaAttachment   *MediaAttachment `bun:"rel:belongs-to"` | 	Sensitive               bool             `validate:"-" bun:",nullzero,default:false"`                                                                            // Set posts from this account to sensitive by default? | ||||||
| 	// For a non-local account, where can the header be fetched? | 	Language                string           `validate:"omitempty,bcp47_language_tag" bun:",nullzero,notnull,default:'en'"`                                          // What language does this account post in? | ||||||
| 	HeaderRemoteURL string `bun:",nullzero"` | 	URI                     string           `validate:"required,url" bun:",nullzero,notnull,unique"`                                                                // ActivityPub URI for this account. | ||||||
| 	// DisplayName for this account. Can be empty, then just the Username will be used for display purposes. | 	URL                     string           `validate:"required_without=Domain,omitempty,url" bun:",nullzero,unique"`                                               // Web URL for this account's profile | ||||||
| 	DisplayName string `bun:",nullzero"` | 	LastWebfingeredAt       time.Time        `validate:"required_with=Domain" bun:"type:timestamp,nullzero"`                                                         // Last time this account was refreshed/located with webfinger. | ||||||
| 	// a key/value map of fields that this account has added to their profile | 	InboxURI                string           `validate:"required_without=Domain,omitempty,url" bun:",nullzero,unique"`                                               // Address of this account's ActivityPub inbox, for sending activity to | ||||||
| 	Fields []Field | 	OutboxURI               string           `validate:"required_without=Domain,omitempty,url" bun:",nullzero,unique"`                                               // Address of this account's activitypub outbox | ||||||
| 	// A note that this account has on their profile (ie., the account's bio/description of themselves) | 	FollowingURI            string           `validate:"required_without=Domain,omitempty,url" bun:",nullzero,unique"`                                               // URI for getting the following list of this account | ||||||
| 	Note string `bun:",nullzero"` | 	FollowersURI            string           `validate:"required_without=Domain,omitempty,url" bun:",nullzero,unique"`                                               // URI for getting the followers list of this account | ||||||
| 	// Is this a memorial account, ie., has the user passed away? | 	FeaturedCollectionURI   string           `validate:"required_without=Domain,omitempty,url" bun:",nullzero,unique"`                                               // URL for getting the featured collection list of this account | ||||||
| 	Memorial bool `bun:",nullzero"` | 	ActorType               string           `validate:"oneof=Application Group Organization Person Service" bun:",nullzero,notnull"`                                // What type of activitypub actor is this account? | ||||||
| 	// This account has moved this account id in the database | 	PrivateKey              *rsa.PrivateKey  `validate:"required_without=Domain"`                                                                                    // Privatekey for validating activitypub requests, will only be defined for local accounts | ||||||
| 	MovedToAccountID string `bun:"type:CHAR(26),nullzero"` | 	PublicKey               *rsa.PublicKey   `validate:"required"`                                                                                                   // Publickey for encoding activitypub requests, will be defined for both local and remote accounts | ||||||
| 	// When was this account created? | 	PublicKeyURI            string           `validate:"required,url" bun:",nullzero,notnull,unique"`                                                                // Web-reachable location of this account's public key | ||||||
| 	CreatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"` | 	SensitizedAt            time.Time        `validate:"-" bun:"type:timestamp,nullzero"`                                                                            // When was this account set to have all its media shown as sensitive? | ||||||
| 	// When was this account last updated? | 	SilencedAt              time.Time        `validate:"-" bun:"type:timestamp,nullzero"`                                                                            // When was this account silenced (eg., statuses only visible to followers, not public)? | ||||||
| 	UpdatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"` | 	SuspendedAt             time.Time        `validate:"-" bun:"type:timestamp,nullzero"`                                                                            // When was this account suspended (eg., don't allow it to log in/post, don't accept media/posts from this account) | ||||||
| 	// Does this account identify itself as a bot? | 	HideCollections         bool             `validate:"-" bun:",nullzero,default:false"`                                                                            // Hide this account's collections | ||||||
| 	Bot bool | 	SuspensionOrigin        string           `validate:"omitempty,ulid" bun:"type:CHAR(26),nullzero"`                                                                // id of the database entry that caused this account to become suspended -- can be an account ID or a domain block ID | ||||||
| 	// What reason was given for signing up when this account was created? |  | ||||||
| 	Reason string `bun:",nullzero"` |  | ||||||
| 
 |  | ||||||
| 	/* |  | ||||||
| 		USER AND PRIVACY PREFERENCES |  | ||||||
| 	*/ |  | ||||||
| 
 |  | ||||||
| 	// Does this account need an approval for new followers? |  | ||||||
| 	Locked bool `bun:",default:true"` |  | ||||||
| 	// Should this account be shown in the instance's profile directory? |  | ||||||
| 	Discoverable bool `bun:",default:false"` |  | ||||||
| 	// Default post privacy for this account |  | ||||||
| 	Privacy Visibility `bun:",default:'public'"` |  | ||||||
| 	// Set posts from this account to sensitive by default? |  | ||||||
| 	Sensitive bool `bun:",default:false"` |  | ||||||
| 	// What language does this account post in? |  | ||||||
| 	Language string `bun:",default:'en'"` |  | ||||||
| 
 |  | ||||||
| 	/* |  | ||||||
| 		ACTIVITYPUB THINGS |  | ||||||
| 	*/ |  | ||||||
| 
 |  | ||||||
| 	// What is the activitypub URI for this account discovered by webfinger? |  | ||||||
| 	URI string `bun:",unique,nullzero"` |  | ||||||
| 	// At which URL can we see the user account in a web browser? |  | ||||||
| 	URL string `bun:",unique,nullzero"` |  | ||||||
| 	// Last time this account was located using the webfinger API. |  | ||||||
| 	LastWebfingeredAt time.Time `bun:",nullzero"` |  | ||||||
| 	// Address of this account's activitypub inbox, for sending activity to |  | ||||||
| 	InboxURI string `bun:",unique,nullzero"` |  | ||||||
| 	// Address of this account's activitypub outbox |  | ||||||
| 	OutboxURI string `bun:",unique,nullzero"` |  | ||||||
| 	// URI for getting the following list of this account |  | ||||||
| 	FollowingURI string `bun:",unique,nullzero"` |  | ||||||
| 	// URI for getting the followers list of this account |  | ||||||
| 	FollowersURI string `bun:",unique,nullzero"` |  | ||||||
| 	// URL for getting the featured collection list of this account |  | ||||||
| 	FeaturedCollectionURI string `bun:",unique,nullzero"` |  | ||||||
| 	// What type of activitypub actor is this account? |  | ||||||
| 	ActorType string `bun:",nullzero"` |  | ||||||
| 	// This account is associated with x account id |  | ||||||
| 	AlsoKnownAs string `bun:",nullzero"` |  | ||||||
| 
 |  | ||||||
| 	/* |  | ||||||
| 		CRYPTO FIELDS |  | ||||||
| 	*/ |  | ||||||
| 
 |  | ||||||
| 	// Privatekey for validating activitypub requests, will only be defined for local accounts |  | ||||||
| 	PrivateKey *rsa.PrivateKey |  | ||||||
| 	// Publickey for encoding activitypub requests, will be defined for both local and remote accounts |  | ||||||
| 	PublicKey *rsa.PublicKey |  | ||||||
| 	// Web-reachable location of this account's public key |  | ||||||
| 	PublicKeyURI string `bun:",nullzero"` |  | ||||||
| 
 |  | ||||||
| 	/* |  | ||||||
| 		ADMIN FIELDS |  | ||||||
| 	*/ |  | ||||||
| 
 |  | ||||||
| 	// When was this account set to have all its media shown as sensitive? |  | ||||||
| 	SensitizedAt time.Time `bun:",nullzero"` |  | ||||||
| 	// When was this account silenced (eg., statuses only visible to followers, not public)? |  | ||||||
| 	SilencedAt time.Time `bun:",nullzero"` |  | ||||||
| 	// When was this account suspended (eg., don't allow it to log in/post, don't accept media/posts from this account) |  | ||||||
| 	SuspendedAt time.Time `bun:",nullzero"` |  | ||||||
| 	// Should we hide this account's collections? |  | ||||||
| 	HideCollections bool |  | ||||||
| 	// id of the database entry that caused this account to become suspended -- can be an account ID or a domain block ID |  | ||||||
| 	SuspensionOrigin string `bun:"type:CHAR(26),nullzero"` |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Field represents a key value field on an account, for things like pronouns, website, etc. | // Field represents a key value field on an account, for things like pronouns, website, etc. | ||||||
| // VerifiedAt is optional, to be used only if Value is a URL to a webpage that contains the | // VerifiedAt is optional, to be used only if Value is a URL to a webpage that contains the | ||||||
| // username of the user. | // username of the user. | ||||||
| type Field struct { | type Field struct { | ||||||
| 	Name       string | 	Name       string    `validate:"required"`          // Name of this field. | ||||||
| 	Value      string | 	Value      string    `validate:"required"`          // Value of this field. | ||||||
| 	VerifiedAt time.Time `bun:",nullzero"` | 	VerifiedAt time.Time `validate:"-" bun:",nullzero"` // This field was verified at (optional). | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Relationship describes a requester's relationship with another account. | ||||||
|  | type Relationship struct { | ||||||
|  | 	ID                  string // The account id. | ||||||
|  | 	Following           bool   // Are you following this user? | ||||||
|  | 	ShowingReblogs      bool   // Are you receiving this user's boosts in your home timeline? | ||||||
|  | 	Notifying           bool   // Have you enabled notifications for this user? | ||||||
|  | 	FollowedBy          bool   // Are you followed by this user? | ||||||
|  | 	Blocking            bool   // Are you blocking this user? | ||||||
|  | 	BlockedBy           bool   // Is this user blocking you? | ||||||
|  | 	Muting              bool   // Are you muting this user? | ||||||
|  | 	MutingNotifications bool   // Are you muting notifications from this user? | ||||||
|  | 	Requested           bool   // Do you have a pending follow request for this user? | ||||||
|  | 	DomainBlocking      bool   // Are you blocking this user's domain? | ||||||
|  | 	Endorsed            bool   // Are you featuring this user on your profile? | ||||||
|  | 	Note                string // Your note on this account. | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,122 +0,0 @@ | ||||||
| /* |  | ||||||
|    GoToSocial |  | ||||||
|    Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org |  | ||||||
| 
 |  | ||||||
|    This program is free software: you can redistribute it and/or modify |  | ||||||
|    it under the terms of the GNU Affero General Public License as published by |  | ||||||
|    the Free Software Foundation, either version 3 of the License, or |  | ||||||
|    (at your option) any later version. |  | ||||||
| 
 |  | ||||||
|    This program is distributed in the hope that it will be useful, |  | ||||||
|    but WITHOUT ANY WARRANTY; without even the implied warranty of |  | ||||||
|    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the |  | ||||||
|    GNU Affero General Public License for more details. |  | ||||||
| 
 |  | ||||||
|    You should have received a copy of the GNU Affero General Public License |  | ||||||
|    along with this program.  If not, see <http://www.gnu.org/licenses/>. |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
| package gtsmodel |  | ||||||
| 
 |  | ||||||
| const ( |  | ||||||
| 	// ActivityStreamsArticle https://www.w3.org/TR/activitystreams-vocabulary/#dfn-article |  | ||||||
| 	ActivityStreamsArticle = "Article" |  | ||||||
| 	// ActivityStreamsAudio https://www.w3.org/TR/activitystreams-vocabulary/#dfn-audio |  | ||||||
| 	ActivityStreamsAudio = "Audio" |  | ||||||
| 	// ActivityStreamsDocument https://www.w3.org/TR/activitystreams-vocabulary/#dfn-document |  | ||||||
| 	ActivityStreamsDocument = "Document" |  | ||||||
| 	// ActivityStreamsEvent https://www.w3.org/TR/activitystreams-vocabulary/#dfn-event |  | ||||||
| 	ActivityStreamsEvent = "Event" |  | ||||||
| 	// ActivityStreamsImage https://www.w3.org/TR/activitystreams-vocabulary/#dfn-image |  | ||||||
| 	ActivityStreamsImage = "Image" |  | ||||||
| 	// ActivityStreamsNote https://www.w3.org/TR/activitystreams-vocabulary/#dfn-note |  | ||||||
| 	ActivityStreamsNote = "Note" |  | ||||||
| 	// ActivityStreamsPage https://www.w3.org/TR/activitystreams-vocabulary/#dfn-page |  | ||||||
| 	ActivityStreamsPage = "Page" |  | ||||||
| 	// ActivityStreamsPlace https://www.w3.org/TR/activitystreams-vocabulary/#dfn-place |  | ||||||
| 	ActivityStreamsPlace = "Place" |  | ||||||
| 	// ActivityStreamsProfile https://www.w3.org/TR/activitystreams-vocabulary/#dfn-profile |  | ||||||
| 	ActivityStreamsProfile = "Profile" |  | ||||||
| 	// ActivityStreamsRelationship https://www.w3.org/TR/activitystreams-vocabulary/#dfn-relationship |  | ||||||
| 	ActivityStreamsRelationship = "Relationship" |  | ||||||
| 	// ActivityStreamsTombstone https://www.w3.org/TR/activitystreams-vocabulary/#dfn-tombstone |  | ||||||
| 	ActivityStreamsTombstone = "Tombstone" |  | ||||||
| 	// ActivityStreamsVideo https://www.w3.org/TR/activitystreams-vocabulary/#dfn-video |  | ||||||
| 	ActivityStreamsVideo = "Video" |  | ||||||
| 	//ActivityStreamsCollection https://www.w3.org/TR/activitystreams-vocabulary/#dfn-collection |  | ||||||
| 	ActivityStreamsCollection = "Collection" |  | ||||||
| 	// ActivityStreamsCollectionPage https://www.w3.org/TR/activitystreams-vocabulary/#dfn-collectionpage |  | ||||||
| 	ActivityStreamsCollectionPage = "CollectionPage" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| const ( |  | ||||||
| 	// ActivityStreamsApplication https://www.w3.org/TR/activitystreams-vocabulary/#dfn-application |  | ||||||
| 	ActivityStreamsApplication = "Application" |  | ||||||
| 	// ActivityStreamsGroup https://www.w3.org/TR/activitystreams-vocabulary/#dfn-group |  | ||||||
| 	ActivityStreamsGroup = "Group" |  | ||||||
| 	// ActivityStreamsOrganization https://www.w3.org/TR/activitystreams-vocabulary/#dfn-organization |  | ||||||
| 	ActivityStreamsOrganization = "Organization" |  | ||||||
| 	// ActivityStreamsPerson https://www.w3.org/TR/activitystreams-vocabulary/#dfn-person |  | ||||||
| 	ActivityStreamsPerson = "Person" |  | ||||||
| 	// ActivityStreamsService https://www.w3.org/TR/activitystreams-vocabulary/#dfn-service |  | ||||||
| 	ActivityStreamsService = "Service" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| const ( |  | ||||||
| 	// ActivityStreamsAccept https://www.w3.org/TR/activitystreams-vocabulary/#dfn-accept |  | ||||||
| 	ActivityStreamsAccept = "Accept" |  | ||||||
| 	// ActivityStreamsAdd https://www.w3.org/TR/activitystreams-vocabulary/#dfn-add |  | ||||||
| 	ActivityStreamsAdd = "Add" |  | ||||||
| 	// ActivityStreamsAnnounce https://www.w3.org/TR/activitystreams-vocabulary/#dfn-announce |  | ||||||
| 	ActivityStreamsAnnounce = "Announce" |  | ||||||
| 	// ActivityStreamsArrive https://www.w3.org/TR/activitystreams-vocabulary/#dfn-arrive |  | ||||||
| 	ActivityStreamsArrive = "Arrive" |  | ||||||
| 	// ActivityStreamsBlock https://www.w3.org/TR/activitystreams-vocabulary/#dfn-block |  | ||||||
| 	ActivityStreamsBlock = "Block" |  | ||||||
| 	// ActivityStreamsCreate https://www.w3.org/TR/activitystreams-vocabulary/#dfn-create |  | ||||||
| 	ActivityStreamsCreate = "Create" |  | ||||||
| 	// ActivityStreamsDelete https://www.w3.org/TR/activitystreams-vocabulary/#dfn-delete |  | ||||||
| 	ActivityStreamsDelete = "Delete" |  | ||||||
| 	// ActivityStreamsDislike https://www.w3.org/TR/activitystreams-vocabulary/#dfn-dislike |  | ||||||
| 	ActivityStreamsDislike = "Dislike" |  | ||||||
| 	// ActivityStreamsFlag https://www.w3.org/TR/activitystreams-vocabulary/#dfn-flag |  | ||||||
| 	ActivityStreamsFlag = "Flag" |  | ||||||
| 	// ActivityStreamsFollow https://www.w3.org/TR/activitystreams-vocabulary/#dfn-follow |  | ||||||
| 	ActivityStreamsFollow = "Follow" |  | ||||||
| 	// ActivityStreamsIgnore https://www.w3.org/TR/activitystreams-vocabulary/#dfn-ignore |  | ||||||
| 	ActivityStreamsIgnore = "Ignore" |  | ||||||
| 	// ActivityStreamsInvite https://www.w3.org/TR/activitystreams-vocabulary/#dfn-invite |  | ||||||
| 	ActivityStreamsInvite = "Invite" |  | ||||||
| 	// ActivityStreamsJoin https://www.w3.org/TR/activitystreams-vocabulary/#dfn-join |  | ||||||
| 	ActivityStreamsJoin = "Join" |  | ||||||
| 	// ActivityStreamsLeave https://www.w3.org/TR/activitystreams-vocabulary/#dfn-leave |  | ||||||
| 	ActivityStreamsLeave = "Leave" |  | ||||||
| 	// ActivityStreamsLike https://www.w3.org/TR/activitystreams-vocabulary/#dfn-like |  | ||||||
| 	ActivityStreamsLike = "Like" |  | ||||||
| 	// ActivityStreamsListen https://www.w3.org/TR/activitystreams-vocabulary/#dfn-listen |  | ||||||
| 	ActivityStreamsListen = "Listen" |  | ||||||
| 	// ActivityStreamsMove https://www.w3.org/TR/activitystreams-vocabulary/#dfn-move |  | ||||||
| 	ActivityStreamsMove = "Move" |  | ||||||
| 	// ActivityStreamsOffer https://www.w3.org/TR/activitystreams-vocabulary/#dfn-offer |  | ||||||
| 	ActivityStreamsOffer = "Offer" |  | ||||||
| 	// ActivityStreamsQuestion https://www.w3.org/TR/activitystreams-vocabulary/#dfn-question |  | ||||||
| 	ActivityStreamsQuestion = "Question" |  | ||||||
| 	// ActivityStreamsReject https://www.w3.org/TR/activitystreams-vocabulary/#dfn-reject |  | ||||||
| 	ActivityStreamsReject = "Reject" |  | ||||||
| 	// ActivityStreamsRead https://www.w3.org/TR/activitystreams-vocabulary/#dfn-read |  | ||||||
| 	ActivityStreamsRead = "Read" |  | ||||||
| 	// ActivityStreamsRemove https://www.w3.org/TR/activitystreams-vocabulary/#dfn-remove |  | ||||||
| 	ActivityStreamsRemove = "Remove" |  | ||||||
| 	// ActivityStreamsTentativeReject https://www.w3.org/TR/activitystreams-vocabulary/#dfn-tentativereject |  | ||||||
| 	ActivityStreamsTentativeReject = "TentativeReject" |  | ||||||
| 	// ActivityStreamsTentativeAccept https://www.w3.org/TR/activitystreams-vocabulary/#dfn-tentativeaccept |  | ||||||
| 	ActivityStreamsTentativeAccept = "TentativeAccept" |  | ||||||
| 	// ActivityStreamsTravel https://www.w3.org/TR/activitystreams-vocabulary/#dfn-travel |  | ||||||
| 	ActivityStreamsTravel = "Travel" |  | ||||||
| 	// ActivityStreamsUndo https://www.w3.org/TR/activitystreams-vocabulary/#dfn-undo |  | ||||||
| 	ActivityStreamsUndo = "Undo" |  | ||||||
| 	// ActivityStreamsUpdate https://www.w3.org/TR/activitystreams-vocabulary/#dfn-update |  | ||||||
| 	ActivityStreamsUpdate = "Update" |  | ||||||
| 	// ActivityStreamsView https://www.w3.org/TR/activitystreams-vocabulary/#dfn-view |  | ||||||
| 	ActivityStreamsView = "View" |  | ||||||
| ) |  | ||||||
|  | @ -18,23 +18,18 @@ | ||||||
| 
 | 
 | ||||||
| package gtsmodel | package gtsmodel | ||||||
| 
 | 
 | ||||||
|  | import "time" | ||||||
|  | 
 | ||||||
| // Application represents an application that can perform actions on behalf of a user. | // Application represents an application that can perform actions on behalf of a user. | ||||||
| // It is used to authorize tokens etc, and is associated with an oauth client id in the database. | // It is used to authorize tokens etc, and is associated with an oauth client id in the database. | ||||||
| type Application struct { | type Application struct { | ||||||
| 	// id of this application in the db | 	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"` | 	CreatedAt    time.Time `validate:"-" bun:"type:timestamp,nullzero,notnull,default:current_timestamp"` // when was item created | ||||||
| 	// name of the application given when it was created (eg., 'tusky') | 	UpdatedAt    time.Time `validate:"-" bun:"type:timestamp,nullzero,notnull,default:current_timestamp"` // when was item last updated | ||||||
| 	Name string `bun:",nullzero"` | 	Name         string    `validate:"required" bun:",nullzero,notnull"`                                  // name of the application given when it was created (eg., 'tusky') | ||||||
| 	// website for the application given when it was created (eg., 'https://tusky.app') | 	Website      string    `validate:"omitempty,url" bun:",nullzero"`                                     // website for the application given when it was created (eg., 'https://tusky.app') | ||||||
| 	Website string `bun:",nullzero"` | 	RedirectURI  string    `validate:"required,uri" bun:",nullzero,notnull"`                              // redirect uri requested by the application for oauth2 flow | ||||||
| 	// redirect uri requested by the application for oauth2 flow | 	ClientID     string    `validate:"required,ulid" bun:"type:CHAR(26),nullzero,notnull"`                // id of the associated oauth client entity in the db | ||||||
| 	RedirectURI string `bun:",nullzero"` | 	ClientSecret string    `validate:"required,uuid" bun:",nullzero,notnull"`                             // secret of the associated oauth client entity in the db | ||||||
| 	// id of the associated oauth client entity in the db | 	Scopes       string    `validate:"required" bun:",nullzero,notnull"`                                  // scopes requested when this app was created | ||||||
| 	ClientID string `bun:"type:CHAR(26),nullzero"` |  | ||||||
| 	// secret of the associated oauth client entity in the db |  | ||||||
| 	ClientSecret string `bun:",nullzero"` |  | ||||||
| 	// scopes requested when this app was created |  | ||||||
| 	Scopes string `bun:",nullzero"` |  | ||||||
| 	// a vapid key generated for this app when it was created |  | ||||||
| 	VapidKey string `bun:",nullzero"` |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,21 +1,33 @@ | ||||||
|  | /* | ||||||
|  |    GoToSocial | ||||||
|  |    Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org | ||||||
|  | 
 | ||||||
|  |    This program is free software: you can redistribute it and/or modify | ||||||
|  |    it under the terms of the GNU Affero General Public License as published by | ||||||
|  |    the Free Software Foundation, either version 3 of the License, or | ||||||
|  |    (at your option) any later version. | ||||||
|  | 
 | ||||||
|  |    This program is distributed in the hope that it will be useful, | ||||||
|  |    but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  |    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  |    GNU Affero General Public License for more details. | ||||||
|  | 
 | ||||||
|  |    You should have received a copy of the GNU Affero General Public License | ||||||
|  |    along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  | */ | ||||||
|  | 
 | ||||||
| package gtsmodel | package gtsmodel | ||||||
| 
 | 
 | ||||||
| import "time" | import "time" | ||||||
| 
 | 
 | ||||||
| // Block refers to the blocking of one account by another. | // Block refers to the blocking of one account by another. | ||||||
| type Block struct { | type Block struct { | ||||||
| 	// id of this block 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"` | 	CreatedAt       time.Time `validate:"-" bun:"type:timestamp,nullzero,notnull,default:current_timestamp"` // when was item created | ||||||
| 	// When was this block created | 	UpdatedAt       time.Time `validate:"-" bun:"type:timestamp,nullzero,notnull,default:current_timestamp"` // when was item last updated | ||||||
| 	CreatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"` | 	URI             string    `validate:"required,url" bun:",notnull,nullzero,unique"`                       // ActivityPub uri of this block. | ||||||
| 	// When was this block updated | 	AccountID       string    `validate:"required,ulid" bun:"type:CHAR(26),unique:blocksrctarget,notnull"`   // Who does this block originate from? | ||||||
| 	UpdatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"` | 	Account         *Account  `validate:"-" bun:"rel:belongs-to"`                                            // Account corresponding to accountID | ||||||
| 	// Who created this block? | 	TargetAccountID string    `validate:"required,ulid" bun:"type:CHAR(26),unique:blocksrctarget,notnull"`   // Who is the target of this block ? | ||||||
| 	AccountID string   `bun:"type:CHAR(26),notnull"` | 	TargetAccount   *Account  `validate:"-" bun:"rel:belongs-to"`                                            // Account corresponding to targetAccountID | ||||||
| 	Account   *Account `bun:"rel:belongs-to"` |  | ||||||
| 	// Who is targeted by this block? |  | ||||||
| 	TargetAccountID string   `bun:"type:CHAR(26),notnull"` |  | ||||||
| 	TargetAccount   *Account `bun:"rel:belongs-to"` |  | ||||||
| 	// Activitypub URI for this block |  | ||||||
| 	URI string `bun:",notnull"` |  | ||||||
| } | } | ||||||
|  |  | ||||||
							
								
								
									
										31
									
								
								internal/gtsmodel/client.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								internal/gtsmodel/client.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,31 @@ | ||||||
|  | /* | ||||||
|  |    GoToSocial | ||||||
|  |    Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org | ||||||
|  | 
 | ||||||
|  |    This program is free software: you can redistribute it and/or modify | ||||||
|  |    it under the terms of the GNU Affero General Public License as published by | ||||||
|  |    the Free Software Foundation, either version 3 of the License, or | ||||||
|  |    (at your option) any later version. | ||||||
|  | 
 | ||||||
|  |    This program is distributed in the hope that it will be useful, | ||||||
|  |    but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  |    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  |    GNU Affero General Public License for more details. | ||||||
|  | 
 | ||||||
|  |    You should have received a copy of the GNU Affero General Public License | ||||||
|  |    along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  | */ | ||||||
|  | 
 | ||||||
|  | package gtsmodel | ||||||
|  | 
 | ||||||
|  | import "time" | ||||||
|  | 
 | ||||||
|  | // Client is a wrapper for OAuth client details. | ||||||
|  | type Client struct { | ||||||
|  | 	ID        string    `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"`      // id of this item in the database | ||||||
|  | 	CreatedAt time.Time `validate:"-" bun:"type:timestamp,nullzero,notnull,default:current_timestamp"` // when was item created | ||||||
|  | 	UpdatedAt time.Time `validate:"-" bun:"type:timestamp,nullzero,notnull,default:current_timestamp"` // when was item last updated | ||||||
|  | 	Secret    string    `validate:"required,uuid" bun:",nullzero,notnull"`                             // secret generated when client was created | ||||||
|  | 	Domain    string    `validate:"required,uri" bun:",nullzero,notnull"`                              // domain requested for client | ||||||
|  | 	UserID    string    `validate:"omitempty,ulid" bun:"type:CHAR(26),nullzero"`                       // id of the user that this client acts on behalf of | ||||||
|  | } | ||||||
|  | @ -22,23 +22,14 @@ import "time" | ||||||
| 
 | 
 | ||||||
| // DomainBlock represents a federation block against a particular domain | // DomainBlock represents a federation block against a particular domain | ||||||
| type DomainBlock struct { | type DomainBlock struct { | ||||||
| 	// ID of this block 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:"-" bun:"type:timestamp,nullzero,notnull,default:current_timestamp"` // when was item created | ||||||
| 	// blocked domain | 	UpdatedAt          time.Time `validate:"-" bun:"type:timestamp,nullzero,notnull,default:current_timestamp"` // when was item last updated | ||||||
| 	Domain string `bun:",pk,notnull,unique"` | 	Domain             string    `validate:"required,fqdn" bun:",nullzero,notnull"`                             // domain to block. Eg. 'whatever.com' | ||||||
| 	// When was this block created | 	CreatedByAccountID string    `validate:"required,ulid" bun:"type:CHAR(26),nullzero,notnull"`                // Account ID of the creator of this block | ||||||
| 	CreatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"` | 	CreatedByAccount   *Account  `validate:"-" bun:"rel:belongs-to"`                                            // Account corresponding to createdByAccountID | ||||||
| 	// When was this block updated | 	PrivateComment     string    `validate:"-" bun:",nullzero"`                                                 // Private comment on this block, viewable to admins | ||||||
| 	UpdatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"` | 	PublicComment      string    `validate:"-" bun:",nullzero"`                                                 // Public comment on this block, viewable (optionally) by everyone | ||||||
| 	// Account ID of the creator of this block | 	Obfuscate          bool      `validate:"-" bun:",nullzero,default:false"`                                   // whether the domain name should appear obfuscated when displaying it publicly | ||||||
| 	CreatedByAccountID string   `bun:"type:CHAR(26),notnull"` | 	SubscriptionID     string    `validate:"omitempty,ulid" bun:"type:CHAR(26),nullzero"`                       // if this block was created through a subscription, what's the subscription ID? | ||||||
| 	CreatedByAccount   *Account `bun:"rel:belongs-to"` |  | ||||||
| 	// Private comment on this block, viewable to admins |  | ||||||
| 	PrivateComment string `bun:",nullzero"` |  | ||||||
| 	// Public comment on this block, viewable (optionally) by everyone |  | ||||||
| 	PublicComment string `bun:",nullzero"` |  | ||||||
| 	// whether the domain name should appear obfuscated when displaying it publicly |  | ||||||
| 	Obfuscate bool |  | ||||||
| 	// if this block was created through a subscription, what's the subscription ID? |  | ||||||
| 	SubscriptionID string `bun:"type:CHAR(26),nullzero"` |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -22,15 +22,10 @@ import "time" | ||||||
| 
 | 
 | ||||||
| // EmailDomainBlock represents a domain that the server should automatically reject sign-up requests from. | // EmailDomainBlock represents a domain that the server should automatically reject sign-up requests from. | ||||||
| type EmailDomainBlock struct { | type EmailDomainBlock struct { | ||||||
| 	// ID of this block 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:"-" bun:"type:timestamp,nullzero,notnull,default:current_timestamp"` // when was item created | ||||||
| 	// Email domain to block. Eg. 'gmail.com' or 'hotmail.com' | 	UpdatedAt          time.Time `validate:"-" bun:"type:timestamp,nullzero,notnull,default:current_timestamp"` // when was item last updated | ||||||
| 	Domain string `bun:",notnull"` | 	Domain             string    `validate:"required,fqdn" bun:",nullzero,notnull"`                             // Email domain to block. Eg. 'gmail.com' or 'hotmail.com' | ||||||
| 	// When was this block created | 	CreatedByAccountID string    `validate:"required,ulid" bun:"type:CHAR(26),nullzero,notnull"`                // Account ID of the creator of this block | ||||||
| 	CreatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"` | 	CreatedByAccount   *Account  `validate:"-" bun:"rel:belongs-to"`                                            // Account corresponding to createdByAccountID | ||||||
| 	// When was this block updated |  | ||||||
| 	UpdatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"` |  | ||||||
| 	// Account ID of the creator of this block |  | ||||||
| 	CreatedByAccountID string   `bun:"type:CHAR(26),notnull"` |  | ||||||
| 	CreatedByAccount   *Account `bun:"rel:belongs-to"` |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -22,56 +22,24 @@ import "time" | ||||||
| 
 | 
 | ||||||
| // Emoji represents a custom emoji that's been uploaded through the admin UI, and is useable by instance denizens. | // Emoji represents a custom emoji that's been uploaded through the admin UI, and is useable by instance denizens. | ||||||
| type Emoji struct { | type Emoji struct { | ||||||
| 	// database ID of this emoji | 	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"` | 	CreatedAt              time.Time `validate:"-" bun:"type:timestamp,nullzero,notnull,default:current_timestamp"`                           // when was item created | ||||||
| 	// String shortcode for this emoji -- the part that's between colons. This should be lowercase a-z_ | 	UpdatedAt              time.Time `validate:"-" bun:"type:timestamp,nullzero,notnull,default:current_timestamp"`                           // when was item last updated | ||||||
| 	// eg., 'blob_hug' 'purple_heart' Must be unique with domain. | 	Shortcode              string    `validate:"required" bun:",notnull,unique:shortcodedomain"`                                              // String shortcode for this emoji -- the part that's between colons. This should be lowercase a-z_  eg., 'blob_hug' 'purple_heart' Must be unique with domain. | ||||||
| 	Shortcode string `bun:",notnull,unique:shortcodedomain"` | 	Domain                 string    `validate:"omitempty,fqdn" bun:",notnull,default:'',unique:shortcodedomain"`                             // Origin domain of this emoji, eg 'example.org', 'queer.party'. empty string for local emojis. | ||||||
| 	// Origin domain of this emoji, eg 'example.org', 'queer.party'. empty string for local emojis. | 	ImageRemoteURL         string    `validate:"required_without=ImageURL,omitempty,url" bun:",nullzero"`                                     // Where can this emoji be retrieved remotely? Null for local emojis. | ||||||
| 	Domain string `bun:",notnull,default:'',unique:shortcodedomain"` | 	ImageStaticRemoteURL   string    `validate:"required_without=ImageStaticURL,omitempty,url" bun:",nullzero"`                               // Where can a static / non-animated version of this emoji be retrieved remotely? Null for local emojis. | ||||||
| 	// When was this emoji created. Must be unique with shortcode. | 	ImageURL               string    `validate:"required_without=ImageRemoteURL,required_without=Domain,omitempty,url" bun:",nullzero"`       // Where can this emoji be retrieved from the local server? Null for remote emojis. | ||||||
| 	CreatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"` | 	ImageStaticURL         string    `validate:"required_without=ImageStaticRemoteURL,required_without=Domain,omitempty,url" bun:",nullzero"` // Where can a static version of this emoji be retrieved from the local server? Null for remote emojis. | ||||||
| 	// When was this emoji updated | 	ImagePath              string    `validate:"required,file" bun:",nullzero,notnull"`                                                       // Path of the emoji image in the server storage system. | ||||||
| 	UpdatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"` | 	ImageStaticPath        string    `validate:"required,file" bun:",nullzero,notnull"`                                                       // Path of a static version of the emoji image in the server storage system | ||||||
| 	// Where can this emoji be retrieved remotely? Null for local emojis. | 	ImageContentType       string    `validate:"required" bun:",nullzero,notnull"`                                                            // MIME content type of the emoji image | ||||||
| 	// For remote emojis, it'll be something like: | 	ImageStaticContentType string    `validate:"required" bun:",nullzero,notnull"`                                                            // MIME content type of the static version of the emoji image. | ||||||
| 	// https://hackers.town/system/custom_emojis/images/000/049/842/original/1b74481204feabfd.png | 	ImageFileSize          int       `validate:"required,min=1" bun:",nullzero,notnull"`                                                      // Size of the emoji image file in bytes, for serving purposes. | ||||||
| 	ImageRemoteURL string `bun:",nullzero"` | 	ImageStaticFileSize    int       `validate:"required,min=1" bun:",nullzero,notnull"`                                                      // Size of the static version of the emoji image file in bytes, for serving purposes. | ||||||
| 	// Where can a static / non-animated version of this emoji be retrieved remotely? Null for local emojis. | 	ImageUpdatedAt         time.Time `validate:"-" bun:"type:timestamp,nullzero,notnull,default:current_timestamp"`                           // When was the emoji image last updated? | ||||||
| 	// For remote emojis, it'll be something like: | 	Disabled               bool      `validate:"-" bun:",notnull,default:false"`                                                              // Has a moderation action disabled this emoji from being shown? | ||||||
| 	// https://hackers.town/system/custom_emojis/images/000/049/842/static/1b74481204feabfd.png | 	URI                    string    `validate:"url" bun:",nullzero,notnull,unique"`                                                          // ActivityPub uri of this emoji. Something like 'https://example.org/emojis/1234' | ||||||
| 	ImageStaticRemoteURL string `bun:",nullzero"` | 	VisibleInPicker        bool      `validate:"-" bun:",notnull,default:true"`                                                               // Is this emoji visible in the admin emoji picker? | ||||||
| 	// Where can this emoji be retrieved from the local server? Null for remote emojis. | 	CategoryID             string    `validate:"omitempty,ulid" bun:"type:CHAR(26),nullzero"`                                                 // In which emoji category is this emoji visible? | ||||||
| 	// Assuming our server is hosted at 'example.org', this will be something like: |  | ||||||
| 	// 'https://example.org/fileserver/6339820e-ef65-4166-a262-5a9f46adb1a7/emoji/original/bfa6c9c5-6c25-4ea4-98b4-d78b8126fb52.png' |  | ||||||
| 	ImageURL string `bun:",nullzero"` |  | ||||||
| 	// Where can a static version of this emoji be retrieved from the local server? Null for remote emojis. |  | ||||||
| 	// Assuming our server is hosted at 'example.org', this will be something like: |  | ||||||
| 	// 'https://example.org/fileserver/6339820e-ef65-4166-a262-5a9f46adb1a7/emoji/small/bfa6c9c5-6c25-4ea4-98b4-d78b8126fb52.png' |  | ||||||
| 	ImageStaticURL string `bun:",nullzero"` |  | ||||||
| 	// Path of the emoji image in the server storage system. Will be something like: |  | ||||||
| 	// '/gotosocial/storage/6339820e-ef65-4166-a262-5a9f46adb1a7/emoji/original/bfa6c9c5-6c25-4ea4-98b4-d78b8126fb52.png' |  | ||||||
| 	ImagePath string `bun:",notnull"` |  | ||||||
| 	// Path of a static version of the emoji image in the server storage system. Will be something like: |  | ||||||
| 	// '/gotosocial/storage/6339820e-ef65-4166-a262-5a9f46adb1a7/emoji/small/bfa6c9c5-6c25-4ea4-98b4-d78b8126fb52.png' |  | ||||||
| 	ImageStaticPath string `bun:",notnull"` |  | ||||||
| 	// MIME content type of the emoji image |  | ||||||
| 	// Probably "image/png" |  | ||||||
| 	ImageContentType string `bun:",notnull"` |  | ||||||
| 	// MIME content type of the static version of the emoji image. |  | ||||||
| 	ImageStaticContentType string `bun:",notnull"` |  | ||||||
| 	// Size of the emoji image file in bytes, for serving purposes. |  | ||||||
| 	ImageFileSize int `bun:",notnull"` |  | ||||||
| 	// Size of the static version of the emoji image file in bytes, for serving purposes. |  | ||||||
| 	ImageStaticFileSize int `bun:",notnull"` |  | ||||||
| 	// When was the emoji image last updated? |  | ||||||
| 	ImageUpdatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"` |  | ||||||
| 	// Has a moderation action disabled this emoji from being shown? |  | ||||||
| 	Disabled bool `bun:",notnull,default:false"` |  | ||||||
| 	// ActivityStreams uri of this emoji. Something like 'https://example.org/emojis/1234' |  | ||||||
| 	URI string `bun:",notnull,unique"` |  | ||||||
| 	// Is this emoji visible in the admin emoji picker? |  | ||||||
| 	VisibleInPicker bool `bun:",notnull,default:true"` |  | ||||||
| 	// In which emoji category is this emoji visible? |  | ||||||
| 	CategoryID string `bun:"type:CHAR(26),nullzero"` |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -22,22 +22,14 @@ import "time" | ||||||
| 
 | 
 | ||||||
| // Follow represents one account following another, and the metadata around that follow. | // Follow represents one account following another, and the metadata around that follow. | ||||||
| type Follow struct { | type Follow struct { | ||||||
| 	// id of this follow 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:"-" bun:"type:timestamp,nullzero,notnull,default:current_timestamp"` // when was item created | ||||||
| 	// When was this follow created? | 	UpdatedAt       time.Time `validate:"-" bun:"type:timestamp,nullzero,notnull,default:current_timestamp"` // when was item last updated | ||||||
| 	CreatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"` | 	URI             string    `validate:"required,url" bun:",notnull,nullzero,unique"`                       // ActivityPub uri of this follow. | ||||||
| 	// When was this follow last updated? | 	AccountID       string    `validate:"required,ulid" bun:"type:CHAR(26),unique:srctarget,notnull"`        // Who does this follow originate from? | ||||||
| 	UpdatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"` | 	Account         *Account  `validate:"-" bun:"rel:belongs-to"`                                            // Account corresponding to accountID | ||||||
| 	// Who does this follow belong to? | 	TargetAccountID string    `validate:"required,ulid" bun:"type:CHAR(26),unique:srctarget,notnull"`        // Who is the target of this follow ? | ||||||
| 	AccountID string   `bun:"type:CHAR(26),unique:srctarget,notnull"` | 	TargetAccount   *Account  `validate:"-" bun:"rel:belongs-to"`                                            // Account corresponding to targetAccountID | ||||||
| 	Account   *Account `bun:"rel:belongs-to"` | 	ShowReblogs     bool      `validate:"-" bun:",nullzero,default:true"`                                    // Does this follow also want to see reblogs and not just posts? | ||||||
| 	// Who does AccountID follow? | 	Notify          bool      `validate:"-" bun:",nullzero,default:false"`                                   // does the following account want to be notified when the followed account posts? | ||||||
| 	TargetAccountID string   `bun:"type:CHAR(26),unique:srctarget,notnull"` |  | ||||||
| 	TargetAccount   *Account `bun:"rel:belongs-to"` |  | ||||||
| 	// Does this follow also want to see reblogs and not just posts? |  | ||||||
| 	ShowReblogs bool `bun:"default:true"` |  | ||||||
| 	// What is the activitypub URI of this follow? |  | ||||||
| 	URI string `bun:",unique,nullzero"` |  | ||||||
| 	// does the following account want to be notified when the followed account posts? |  | ||||||
| 	Notify bool |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -22,22 +22,14 @@ import "time" | ||||||
| 
 | 
 | ||||||
| // FollowRequest represents one account requesting to follow another, and the metadata around that request. | // FollowRequest represents one account requesting to follow another, and the metadata around that request. | ||||||
| type FollowRequest struct { | type FollowRequest struct { | ||||||
| 	// id of this follow request 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:"-" bun:"type:timestamp,nullzero,notnull,default:current_timestamp"` // when was item created | ||||||
| 	// When was this follow request created? | 	UpdatedAt       time.Time `validate:"-" bun:"type:timestamp,nullzero,notnull,default:current_timestamp"` // when was item last updated | ||||||
| 	CreatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"` | 	URI             string    `validate:"required,url" bun:",notnull,nullzero,unique"`                       // ActivityPub uri of this follow (request). | ||||||
| 	// When was this follow request last updated? | 	AccountID       string    `validate:"required,ulid" bun:"type:CHAR(26),unique:frsrctarget,notnull"`      // Who does this follow request originate from? | ||||||
| 	UpdatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"` | 	Account         *Account  `validate:"-" bun:"rel:belongs-to"`                                            // Account corresponding to accountID | ||||||
| 	// Who does this follow request originate from? | 	TargetAccountID string    `validate:"required,ulid" bun:"type:CHAR(26),unique:frsrctarget,notnull"`      // Who is the target of this follow request? | ||||||
| 	AccountID string   `bun:"type:CHAR(26),unique:frsrctarget,notnull"` | 	TargetAccount   *Account  `validate:"-" bun:"rel:belongs-to"`                                            // Account corresponding to targetAccountID | ||||||
| 	Account   *Account `bun:"rel:belongs-to"` | 	ShowReblogs     bool      `validate:"-" bun:",nullzero,default:true"`                                    // Does this follow also want to see reblogs and not just posts? | ||||||
| 	// Who is the target of this follow request? | 	Notify          bool      `validate:"-" bun:",nullzero,default:false"`                                   // does the following account want to be notified when the followed account posts? | ||||||
| 	TargetAccountID string   `bun:"type:CHAR(26),unique:frsrctarget,notnull"` |  | ||||||
| 	TargetAccount   *Account `bun:"rel:belongs-to"` |  | ||||||
| 	// Does this follow also want to see reblogs and not just posts? |  | ||||||
| 	ShowReblogs bool `bun:"default:true"` |  | ||||||
| 	// What is the activitypub URI of this follow request? |  | ||||||
| 	URI string `bun:",unique,nullzero"` |  | ||||||
| 	// does the following account want to be notified when the followed account posts? |  | ||||||
| 	Notify bool |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,41 +1,43 @@ | ||||||
|  | /* | ||||||
|  |    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 | package gtsmodel | ||||||
| 
 | 
 | ||||||
| import "time" | import "time" | ||||||
| 
 | 
 | ||||||
| // Instance represents a federated instance, either local or remote. | // Instance represents a federated instance, either local or remote. | ||||||
| type Instance struct { | type Instance struct { | ||||||
| 	// ID of this instance 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:"-" bun:"type:timestamp,nullzero,notnull,default:current_timestamp"`                // when was item created | ||||||
| 	// Instance domain eg example.org | 	UpdatedAt              time.Time    `validate:"-" bun:"type:timestamp,nullzero,notnull,default:current_timestamp"`                // when was item last updated | ||||||
| 	Domain string `bun:",pk,notnull,unique"` | 	Domain                 string       `validate:"required,fqdn" bun:",nullzero,notnull,unique"`                                     // Instance domain eg example.org | ||||||
| 	// Title of this instance as it would like to be displayed. | 	Title                  string       `validate:"-" bun:",nullzero"`                                                                // Title of this instance as it would like to be displayed. | ||||||
| 	Title string `bun:",nullzero"` | 	URI                    string       `validate:"required,url" bun:",nullzero,notnull,unique"`                                      // base URI of this instance eg https://example.org | ||||||
| 	// base URI of this instance eg https://example.org | 	SuspendedAt            time.Time    `validate:"-" bun:"type:timestamp,nullzero"`                                                  // When was this instance suspended, if at all? | ||||||
| 	URI string `bun:",notnull,unique"` | 	DomainBlockID          string       `validate:"omitempty,ulid" bun:"type:CHAR(26),nullzero"`                                      // ID of any existing domain block for this instance in the database | ||||||
| 	// When was this instance created in the db? | 	DomainBlock            *DomainBlock `validate:"-" bun:"rel:belongs-to"`                                                           // Domain block corresponding to domainBlockID | ||||||
| 	CreatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"` | 	ShortDescription       string       `validate:"-" bun:",nullzero"`                                                                // Short description of this instance | ||||||
| 	// When was this instance last updated in the db? | 	Description            string       `validate:"-" bun:",nullzero"`                                                                // Longer description of this instance | ||||||
| 	UpdatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"` | 	Terms                  string       `validate:"-" bun:",nullzero"`                                                                // Terms and conditions of this instance | ||||||
| 	// When was this instance suspended, if at all? | 	ContactEmail           string       `validate:"omitempty,email" bun:",nullzero"`                                                  // Contact email address for this instance | ||||||
| 	SuspendedAt time.Time `bun:",nullzero"` | 	ContactAccountUsername string       `validate:"required_with=ContactAccountID" bun:",nullzero"`                                   // Username of the contact account for this instance | ||||||
| 	// ID of any existing domain block for this instance in the database | 	ContactAccountID       string       `validate:"required_with=ContactAccountUsername,omitempty,ulid" bun:"type:CHAR(26),nullzero"` // Contact account ID in the database for this instance | ||||||
| 	DomainBlockID string       `bun:"type:CHAR(26),nullzero"` | 	ContactAccount         *Account     `validate:"-" bun:"rel:belongs-to"`                                                           // account corresponding to contactAccountID | ||||||
| 	DomainBlock   *DomainBlock `bun:"rel:belongs-to"` | 	Reputation             int64        `validate:"-" bun:",notnull,default:0"`                                                       // Reputation score of this instance | ||||||
| 	// Short description of this instance | 	Version                string       `validate:"-" bun:",nullzero"`                                                                // Version of the software used on this instance | ||||||
| 	ShortDescription string `bun:",nullzero"` |  | ||||||
| 	// Longer description of this instance |  | ||||||
| 	Description string `bun:",nullzero"` |  | ||||||
| 	// Terms and conditions of this instance |  | ||||||
| 	Terms string `bun:",nullzero"` |  | ||||||
| 	// Contact email address for this instance |  | ||||||
| 	ContactEmail string `bun:",nullzero"` |  | ||||||
| 	// Username of the contact account for this instance |  | ||||||
| 	ContactAccountUsername string `bun:",nullzero"` |  | ||||||
| 	// Contact account ID in the database for this instance |  | ||||||
| 	ContactAccountID string   `bun:"type:CHAR(26),nullzero"` |  | ||||||
| 	ContactAccount   *Account `bun:"rel:belongs-to"` |  | ||||||
| 	// Reputation score of this instance |  | ||||||
| 	Reputation int64 `bun:",notnull,default:0"` |  | ||||||
| 	// Version of the software used on this instance |  | ||||||
| 	Version string `bun:",nullzero"` |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -25,127 +25,93 @@ 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:"-" bun:"type:timestamp,nullzero,notnull,default:current_timestamp"`                  // when was item created | ||||||
| 	// ID of the status to which this is attached | 	UpdatedAt         time.Time        `validate:"-" bun:"type:timestamp,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:"-" bun:"type:timestamp,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:"-" bun:"type:timestamp,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 | ||||||
| 
 | 
 | ||||||
|  | // MediaAttachment processing states. | ||||||
| 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 | ||||||
| 
 | 
 | ||||||
|  | // MediaAttachment file types. | ||||||
| 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"` | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -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:"-" bun:"type:timestamp,nullzero,notnull,default:current_timestamp"` // when was item created | ||||||
| 	// ID of the status this mention originates from | 	UpdatedAt        time.Time `validate:"-" bun:"type:timestamp,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. | ||||||
| 
 |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -22,41 +22,29 @@ 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:"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"` | 	CreatedAt        time.Time        `validate:"-" bun:"type:timestamp,nullzero,notnull,default:current_timestamp"`                                                                                                                               // when was item created | ||||||
| 	// Type of this notification | 	UpdatedAt        time.Time        `validate:"-" bun:"type:timestamp,nullzero,notnull,default:current_timestamp"`                                                                                                                               // when was item last updated                                                                                                                            // when was item created | ||||||
| 	NotificationType NotificationType `bun:",notnull"` | 	NotificationType NotificationType `validate:"oneof=follow follow_request mention reblog favourite poll status" bun:",nullzero,notnull"`                                                                                                        // Type of this notification | ||||||
| 	// Creation time of this notification | 	TargetAccountID  string           `validate:"ulid" bun:"type:CHAR(26),nullzero,notnull"`                                                                                                                                                       // Which account does this notification target (ie., who will receive the notification?) | ||||||
| 	CreatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"` | 	TargetAccount    *Account         `validate:"-" bun:"rel:belongs-to"`                                                                                                                                                                          // Which account performed the action that created this notification? | ||||||
| 	// Which account does this notification target (ie., who will receive the notification?) | 	OriginAccountID  string           `validate:"ulid" bun:"type:CHAR(26),nullzero,notnull"`                                                                                                                                                       // ID of the account that performed the action that created the notification. | ||||||
| 	TargetAccountID string   `bun:"type:CHAR(26),notnull"` | 	OriginAccount    *Account         `validate:"-" bun:"rel:belongs-to"`                                                                                                                                                                          // Account corresponding to originAccountID | ||||||
| 	TargetAccount   *Account `bun:"rel:belongs-to"` | 	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? | ||||||
| 	// Which account performed the action that created this notification? | 	Status           *Status          `validate:"-" bun:"rel:belongs-to"`                                                                                                                                                                          // Status corresponding to statusID | ||||||
| 	OriginAccountID string   `bun:"type:CHAR(26),notnull"` | 	Read             bool             `validate:"-" bun:",notnull,default:false"`                                                                                                                                                                  // Notification has been seen/read | ||||||
| 	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 | ||||||
| 
 | 
 | ||||||
|  | // Notification Types | ||||||
| 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" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  | @ -1,49 +0,0 @@ | ||||||
| /* |  | ||||||
|    GoToSocial |  | ||||||
|    Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org |  | ||||||
| 
 |  | ||||||
|    This program is free software: you can redistribute it and/or modify |  | ||||||
|    it under the terms of the GNU Affero General Public License as published by |  | ||||||
|    the Free Software Foundation, either version 3 of the License, or |  | ||||||
|    (at your option) any later version. |  | ||||||
| 
 |  | ||||||
|    This program is distributed in the hope that it will be useful, |  | ||||||
|    but WITHOUT ANY WARRANTY; without even the implied warranty of |  | ||||||
|    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the |  | ||||||
|    GNU Affero General Public License for more details. |  | ||||||
| 
 |  | ||||||
|    You should have received a copy of the GNU Affero General Public License |  | ||||||
|    along with this program.  If not, see <http://www.gnu.org/licenses/>. |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
| package gtsmodel |  | ||||||
| 
 |  | ||||||
| // Relationship describes a requester's relationship with another account. |  | ||||||
| type Relationship struct { |  | ||||||
| 	// The account id. |  | ||||||
| 	ID string |  | ||||||
| 	// Are you following this user? |  | ||||||
| 	Following bool |  | ||||||
| 	// Are you receiving this user's boosts in your home timeline? |  | ||||||
| 	ShowingReblogs bool |  | ||||||
| 	// Have you enabled notifications for this user? |  | ||||||
| 	Notifying bool |  | ||||||
| 	// Are you followed by this user? |  | ||||||
| 	FollowedBy bool |  | ||||||
| 	// Are you blocking this user? |  | ||||||
| 	Blocking bool |  | ||||||
| 	// Is this user blocking you? |  | ||||||
| 	BlockedBy bool |  | ||||||
| 	// Are you muting this user? |  | ||||||
| 	Muting bool |  | ||||||
| 	// Are you muting notifications from this user? |  | ||||||
| 	MutingNotifications bool |  | ||||||
| 	// Do you have a pending follow request for this user? |  | ||||||
| 	Requested bool |  | ||||||
| 	// Are you blocking this user's domain? |  | ||||||
| 	DomainBlocking bool |  | ||||||
| 	// Are you featuring this user on your profile? |  | ||||||
| 	Endorsed bool |  | ||||||
| 	// Your note on this account. |  | ||||||
| 	Note string |  | ||||||
| } |  | ||||||
|  | @ -18,9 +18,13 @@ | ||||||
| 
 | 
 | ||||||
| package gtsmodel | package gtsmodel | ||||||
| 
 | 
 | ||||||
|  | import "time" | ||||||
|  | 
 | ||||||
| // RouterSession is used to store and retrieve settings for a router session. | // RouterSession is used to store and retrieve settings for a router session. | ||||||
| type RouterSession struct { | type RouterSession struct { | ||||||
| 	ID    string `bun:"type:CHAR(26),pk,notnull"` | 	ID        string    `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"`      // id of this item in the database | ||||||
| 	Auth  []byte `bun:"type:bytea,notnull,nullzero"` | 	CreatedAt time.Time `validate:"-" bun:"type:timestamp,nullzero,notnull,default:current_timestamp"` // when was item created | ||||||
| 	Crypt []byte `bun:"type:bytea,notnull,nullzero"` | 	UpdatedAt time.Time `validate:"-" bun:"type:timestamp,nullzero,notnull,default:current_timestamp"` // when was item last updated | ||||||
|  | 	Auth      []byte    `validate:"required,len=32" bun:"type:bytea,notnull,nullzero"` | ||||||
|  | 	Crypt     []byte    `validate:"required,len=32" bun:"type:bytea,notnull,nullzero"` | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -24,87 +24,59 @@ import ( | ||||||
| 
 | 
 | ||||||
| // Status represents a user-created 'post' or 'status' in the database, either remote or local | // Status represents a user-created 'post' or 'status' in the database, either remote or local | ||||||
| type Status struct { | type Status struct { | ||||||
| 	// id of the status 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"` | 	CreatedAt                time.Time          `validate:"-" bun:"type:timestamp,nullzero,notnull,default:current_timestamp"`                         // when was item created | ||||||
| 	// uri at which this status is reachable | 	UpdatedAt                time.Time          `validate:"-" bun:"type:timestamp,nullzero,notnull,default:current_timestamp"`                         // when was item last updated | ||||||
| 	URI string `bun:",unique,nullzero"` | 	URI                      string             `validate:"required,url" bun:",unique,nullzero,notnull"`                                               // activitypub URI of this status | ||||||
| 	// web url for viewing this status | 	URL                      string             `validate:"url" bun:",nullzero"`                                                                       // web url for viewing this status | ||||||
| 	URL string `bun:",unique,nullzero"` | 	Content                  string             `validate:"-" bun:",nullzero"`                                                                         // content of this status; likely html-formatted but not guaranteed | ||||||
| 	// the html-formatted content of this status | 	AttachmentIDs            []string           `validate:"dive,ulid" bun:"attachments,array"`                                                         // Database IDs of any media attachments associated with this status | ||||||
| 	Content string `bun:",nullzero"` | 	Attachments              []*MediaAttachment `validate:"-" bun:"attached_media,rel:has-many"`                                                       // Attachments corresponding to attachmentIDs | ||||||
| 	// Database IDs of any media attachments associated with this status | 	TagIDs                   []string           `validate:"dive,ulid" bun:"tags,array"`                                                                // Database IDs of any tags used in this status | ||||||
| 	AttachmentIDs []string           `bun:"attachments,array"` | 	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 | ||||||
| 	Attachments   []*MediaAttachment `bun:"attached_media,rel:has-many"` | 	MentionIDs               []string           `validate:"dive,ulid" bun:"mentions,array"`                                                            // Database IDs of any mentions in this status | ||||||
| 	// Database IDs of any tags used in this status | 	Mentions                 []*Mention         `validate:"-" bun:"attached_mentions,rel:has-many"`                                                    // Mentions corresponding to mentionIDs | ||||||
| 	TagIDs []string `bun:"tags,array"` | 	EmojiIDs                 []string           `validate:"dive,ulid" bun:"emojis,array"`                                                              // Database IDs of any emojis used in this status | ||||||
| 	Tags   []*Tag   `bun:"attached_tags,m2m:status_to_tags"` // 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 | ||||||
| 	// Database IDs of any mentions in this status | 	Local                    bool               `validate:"-" bun:",notnull,default:false"`                                                            // is this status from a local account? | ||||||
| 	MentionIDs []string   `bun:"mentions,array"` | 	AccountID                string             `validate:"required,ulid" bun:"type:CHAR(26),nullzero,notnull"`                                        // which account posted this status? | ||||||
| 	Mentions   []*Mention `bun:"attached_mentions,rel:has-many"` | 	Account                  *Account           `validate:"-" bun:"rel:belongs-to"`                                                                    // account corresponding to accountID | ||||||
| 	// Database IDs of any emojis used in this status | 	AccountURI               string             `validate:"required,url" bun:",nullzero,notnull"`                                                      // activitypub uri of the owner of this status | ||||||
| 	EmojiIDs []string `bun:"emojis,array"` | 	InReplyToID              string             `validate:"required_with=InReplyToURI InReplyToAccountID,omitempty,ulid" bun:"type:CHAR(26),nullzero"` // id of the status this status replies to | ||||||
| 	Emojis   []*Emoji `bun:"attached_emojis,m2m:status_to_emojis"` // https://bun.uptrace.dev/guide/relations.html#many-to-many-relation | 	InReplyToURI             string             `validate:"required_with=InReplyToID InReplyToAccountID,omitempty,url" bun:",nullzero"`                // activitypub uri of the status this status is a reply to | ||||||
| 	// when was this status created? | 	InReplyToAccountID       string             `validate:"required_with=InReplyToID InReplyToURI,omitempty,ulid" bun:"type:CHAR(26),nullzero"`        // id of the account that this status replies to | ||||||
| 	CreatedAt time.Time `bun:",notnull,nullzero,default:current_timestamp"` | 	InReplyTo                *Status            `validate:"-" bun:"-"`                                                                                 // status corresponding to inReplyToID | ||||||
| 	// when was this status updated? | 	InReplyToAccount         *Account           `validate:"-" bun:"rel:belongs-to"`                                                                    // account corresponding to inReplyToAccountID | ||||||
| 	UpdatedAt time.Time `bun:",notnull,nullzero,default:current_timestamp"` | 	BoostOfID                string             `validate:"required_with=BoostOfAccountID,omitempty,ulid" bun:"type:CHAR(26),nullzero"`                // id of the status this status is a boost of | ||||||
| 	// is this status from a local account? | 	BoostOfAccountID         string             `validate:"required_with=BoostOfID,omitempty,ulid" bun:"type:CHAR(26),nullzero"`                       // id of the account that owns the boosted status | ||||||
| 	Local bool | 	BoostOf                  *Status            `validate:"-" bun:"-"`                                                                                 // status that corresponds to boostOfID | ||||||
| 	// which account posted this status? | 	BoostOfAccount           *Account           `validate:"-" bun:"rel:belongs-to"`                                                                    // account that corresponds to boostOfAccountID | ||||||
| 	AccountID string   `bun:"type:CHAR(26),notnull"` | 	ContentWarning           string             `validate:"-" bun:",nullzero"`                                                                         // cw string for this status | ||||||
| 	Account   *Account `bun:"rel:belongs-to"` | 	Visibility               Visibility         `validate:"-" bun:",nullzero,notnull"`                                                                 // visibility entry for this status | ||||||
| 	// AP uri of the owner of this status | 	Sensitive                bool               `validate:"-" bun:",notnull,default:false"`                                                            // mark the status as sensitive? | ||||||
| 	AccountURI string `bun:",nullzero"` | 	Language                 string             `validate:"-" bun:",nullzero"`                                                                         // what language is this status written in? | ||||||
| 	// id of the status this status is a reply to | 	CreatedWithApplicationID string             `validate:"required_if=Local true,omitempty,ulid" bun:"type:CHAR(26),nullzero"`                        // Which application was used to create this status? | ||||||
| 	InReplyToID string  `bun:"type:CHAR(26),nullzero"` | 	CreatedWithApplication   *Application       `validate:"-" bun:"rel:belongs-to"`                                                                    // application corresponding to createdWithApplicationID | ||||||
| 	InReplyTo   *Status `bun:"-"` | 	VisibilityAdvanced       VisibilityAdvanced `validate:"required" bun:",nullzero,notnull" `                                                         // advanced visibility for this status | ||||||
| 	// AP uri of the status this status is a reply to | 	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!. | ||||||
| 	InReplyToURI string `bun:",nullzero"` | 	Text                     string             `validate:"-" bun:",nullzero"`                                                                         // Original text of the status without formatting | ||||||
| 	// id of the account that this status replies to | 	Pinned                   bool               `validate:"-" bun:",notnull,default:false" `                                                           // Has this status been pinned by its owner? | ||||||
| 	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 |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // 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. | ||||||
| type StatusToTag struct { | type StatusToTag struct { | ||||||
| 	StatusID string  `bun:"type:CHAR(26),unique:statustag,nullzero"` | 	StatusID string  `validate:"ulid,required" bun:"type:CHAR(26),unique:statustag,nullzero,notnull"` | ||||||
| 	Status   *Status `bun:"rel:belongs-to"` | 	Status   *Status `validate:"-" bun:"rel:belongs-to"` | ||||||
| 	TagID    string  `bun:"type:CHAR(26),unique:statustag,nullzero"` | 	TagID    string  `validate:"ulid,required" bun:"type:CHAR(26),unique:statustag,nullzero,notnull"` | ||||||
| 	Tag      *Tag    `bun:"rel:belongs-to"` | 	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. | // StatusToEmoji is an intermediate struct to facilitate the many2many relationship between a status and one or more emojis. | ||||||
| type StatusToEmoji struct { | type StatusToEmoji struct { | ||||||
| 	StatusID string  `bun:"type:CHAR(26),unique:statusemoji,nullzero"` | 	StatusID string  `validate:"ulid,required" bun:"type:CHAR(26),unique:statusemoji,nullzero,notnull"` | ||||||
| 	Status   *Status `bun:"rel:belongs-to"` | 	Status   *Status `validate:"-" bun:"rel:belongs-to"` | ||||||
| 	EmojiID  string  `bun:"type:CHAR(26),unique:statusemoji,nullzero"` | 	EmojiID  string  `validate:"ulid,required" bun:"type:CHAR(26),unique:statusemoji,nullzero,notnull"` | ||||||
| 	Emoji    *Emoji  `bun:"rel:belongs-to"` | 	Emoji    *Emoji  `validate:"-" bun:"rel:belongs-to"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Visibility represents the visibility granularity of a status. | // 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. | // If DIRECT is selected, boostable will be FALSE, and all other flags will be TRUE. | ||||||
| type VisibilityAdvanced struct { | type VisibilityAdvanced struct { | ||||||
| 	// 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) | ||||||
| 	Federated bool `bun:"default:true"` | 	Boostable bool `validate:"-" bun:",notnull,default:true"` // This status can be boosted/reblogged | ||||||
| 	// This status can be boosted/reblogged | 	Replyable bool `validate:"-" bun:",notnull,default:true"` // This status can be replied to | ||||||
| 	Boostable bool `bun:"default:true"` | 	Likeable  bool `validate:"-" bun:",notnull,default:true"` // This status can be liked/faved | ||||||
| 	// This status can be replied to |  | ||||||
| 	Replyable bool `bun:"default:true"` |  | ||||||
| 	// This status can be liked/faved |  | ||||||
| 	Likeable bool `bun:"default:true"` |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -20,18 +20,15 @@ package gtsmodel | ||||||
| 
 | 
 | ||||||
| import "time" | 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 { | type StatusBookmark struct { | ||||||
| 	// id of this bookmark 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:"-" bun:"type:timestamp,nullzero,notnull,default:current_timestamp"` // when was item created | ||||||
| 	// when was this bookmark created | 	UpdatedAt       time.Time `validate:"-" bun:"type:timestamp,nullzero,notnull,default:current_timestamp"` // when was item last updated | ||||||
| 	CreatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"` | 	AccountID       string    `validate:"required,ulid" bun:"type:CHAR(26),nullzero,notnull"`                // id of the account that created ('did') the bookmark | ||||||
| 	// id of the account that created ('did') the bookmarking | 	Account         *Account  `validate:"-" bun:"rel:belongs-to"`                                            // account that created the bookmark | ||||||
| 	AccountID string   `bun:"type:CHAR(26),notnull"` | 	TargetAccountID string    `validate:"required,ulid" bun:"type:CHAR(26),nullzero,notnull"`                // id the account owning the bookmarked status | ||||||
| 	Account   *Account `bun:"rel:belongs-to"` | 	TargetAccount   *Account  `validate:"-" bun:"rel:belongs-to"`                                            // account owning the bookmarked status | ||||||
| 	// id the 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 | ||||||
| 	TargetAccountID string   `bun:"type:CHAR(26),notnull"` | 	Status          *Status   `validate:"-" bun:"rel:belongs-to"`                                            // the bookmarked status | ||||||
| 	TargetAccount   *Account `bun:"rel:belongs-to"` |  | ||||||
| 	// database id of the status that has been bookmarked |  | ||||||
| 	StatusID string `bun:"type:CHAR(26),notnull"` |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -22,19 +22,14 @@ import "time" | ||||||
| 
 | 
 | ||||||
| // StatusFave refers to a 'fave' or 'like' in the database, from one account, targeting the status of another account | // StatusFave refers to a 'fave' or 'like' in the database, from one account, targeting the status of another account | ||||||
| type StatusFave struct { | type StatusFave struct { | ||||||
| 	// id of this fave 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:"-" bun:"type:timestamp,nullzero,notnull,default:current_timestamp"` // when was item created | ||||||
| 	// when was this fave created | 	UpdatedAt       time.Time `validate:"-" bun:"type:timestamp,nullzero,notnull,default:current_timestamp"` // when was item last updated | ||||||
| 	CreatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"` | 	AccountID       string    `validate:"required,ulid" bun:"type:CHAR(26),nullzero,notnull"`                // id of the account that created ('did') the fave | ||||||
| 	// id of the account that created ('did') the fave | 	Account         *Account  `validate:"-" bun:"rel:belongs-to"`                                            // account that created the fave | ||||||
| 	AccountID string   `bun:"type:CHAR(26),notnull"` | 	TargetAccountID string    `validate:"required,ulid" bun:"type:CHAR(26),nullzero,notnull"`                // id the account owning the faved status | ||||||
| 	Account   *Account `bun:"rel:belongs-to"` | 	TargetAccount   *Account  `validate:"-" bun:"rel:belongs-to"`                                            // account owning the faved status | ||||||
| 	// id the 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' | ||||||
| 	TargetAccountID string   `bun:"type:CHAR(26),notnull"` | 	Status          *Status   `validate:"-" bun:"rel:belongs-to"`                                            // the faved status | ||||||
| 	TargetAccount   *Account `bun:"rel:belongs-to"` | 	URI             string    `validate:"required,url" bun:",nullzero,notnull"`                              // ActivityPub URI of this fave | ||||||
| 	// 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"` |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -20,19 +20,15 @@ package gtsmodel | ||||||
| 
 | 
 | ||||||
| import "time" | import "time" | ||||||
| 
 | 
 | ||||||
| // StatusMute refers to one account having muted the status of another account or its own | // StatusMute refers to one account having muted the status of another account or its own. | ||||||
| type StatusMute struct { | type StatusMute struct { | ||||||
| 	// id of this mute 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:"-" bun:"type:timestamp,nullzero,notnull,default:current_timestamp"` // when was item created | ||||||
| 	// when was this mute created | 	UpdatedAt       time.Time `validate:"-" bun:"type:timestamp,nullzero,notnull,default:current_timestamp"` // when was item last updated | ||||||
| 	CreatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"` | 	AccountID       string    `validate:"required,ulid" bun:"type:CHAR(26),nullzero,notnull"`                // id of the account that created ('did') the mute | ||||||
| 	// id of the account that created ('did') the mute | 	Account         *Account  `validate:"-" bun:"rel:belongs-to"`                                            // pointer to the account specified by accountID | ||||||
| 	AccountID string   `bun:"type:CHAR(26),notnull"` | 	TargetAccountID string    `validate:"required,ulid" bun:"type:CHAR(26),nullzero,notnull"`                // id the account owning the muted status (can be the same as accountID) | ||||||
| 	Account   *Account `bun:"rel:belongs-to"` | 	TargetAccount   *Account  `validate:"-" bun:"rel:belongs-to"`                                            // pointer to the account specified by targetAccountID | ||||||
| 	// id the account owning the muted status (can be the same as accountID) | 	StatusID        string    `validate:"required,ulid" bun:"type:CHAR(26),nullzero,notnull"`                // database id of the status that has been muted | ||||||
| 	TargetAccountID string   `bun:"type:CHAR(26),notnull"` | 	Status          *Status   `validate:"-" bun:"rel:belongs-to"`                                            // pointer to the muted status specified by statusID | ||||||
| 	TargetAccount   *Account `bun:"rel:belongs-to"` |  | ||||||
| 	// database id of the status that has been muted |  | ||||||
| 	StatusID string  `bun:"type:CHAR(26),notnull"` |  | ||||||
| 	Status   *Status `bun:"rel:belongs-to"` |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -20,24 +20,15 @@ package gtsmodel | ||||||
| 
 | 
 | ||||||
| import "time" | import "time" | ||||||
| 
 | 
 | ||||||
| // Tag represents a hashtag for gathering public statuses together | // Tag represents a hashtag for gathering public statuses together. | ||||||
| type Tag struct { | type Tag struct { | ||||||
| 	// id of this tag 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:",unique,type:CHAR(26),pk,notnull"` | 	CreatedAt              time.Time `validate:"-" bun:"type:timestamp,nullzero,notnull,default:current_timestamp"` // when was item created | ||||||
| 	// Href of this tag, eg https://example.org/tags/somehashtag | 	UpdatedAt              time.Time `validate:"-" bun:"type:timestamp,nullzero,notnull,default:current_timestamp"` // when was item last updated | ||||||
| 	URL string `bun:",nullzero"` | 	URL                    string    `validate:"required,url" bun:",nullzero,notnull"`                              // Href/web address of this tag, eg https://example.org/tags/somehashtag | ||||||
| 	// 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 | ||||||
| 	Name string `bun:",unique,notnull"` | 	FirstSeenFromAccountID string    `validate:"omitempty,ulid" bun:"type:CHAR(26),nullzero"`                       // Which account ID is the first one we saw using this tag? | ||||||
| 	// Which account ID is the first one we saw using this tag? | 	Useable                bool      `validate:"-" bun:",notnull,default:true"`                                     // can our instance users use this tag? | ||||||
| 	FirstSeenFromAccountID string `bun:"type:CHAR(26),nullzero"` | 	Listable               bool      `validate:"-" bun:",notnull,default:true"`                                     // can our instance users look up this tag? | ||||||
| 	// when was this tag created | 	LastStatusAt           time.Time `validate:"-" bun:"type:timestamp,nullzero,notnull,default:current_timestamp"` // when was this tag last used? | ||||||
| 	CreatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"` |  | ||||||
| 	// when was this tag last updated |  | ||||||
| 	UpdatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"` |  | ||||||
| 	// can our instance users use this tag? |  | ||||||
| 	Useable bool `bun:",notnull,default:true"` |  | ||||||
| 	// can our instance users look up this tag? |  | ||||||
| 	Listable bool `bun:",notnull,default:true"` |  | ||||||
| 	// when was this tag last used? |  | ||||||
| 	LastStatusAt time.Time `bun:",nullzero"` |  | ||||||
| } | } | ||||||
|  |  | ||||||
							
								
								
									
										43
									
								
								internal/gtsmodel/token.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								internal/gtsmodel/token.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,43 @@ | ||||||
|  | /* | ||||||
|  |    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 | ||||||
|  | 
 | ||||||
|  | import "time" | ||||||
|  | 
 | ||||||
|  | // Token is a translation of the gotosocial token with the ExpiresIn fields replaced with ExpiresAt. | ||||||
|  | type Token struct { | ||||||
|  | 	ID                  string    `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"`      // id of this item in the database | ||||||
|  | 	CreatedAt           time.Time `validate:"-" bun:"type:timestamp,nullzero,notnull,default:current_timestamp"` // when was item created | ||||||
|  | 	UpdatedAt           time.Time `validate:"-" bun:"type:timestamp,nullzero,notnull,default:current_timestamp"` // when was item last updated | ||||||
|  | 	ClientID            string    `validate:"required,ulid" bun:"type:CHAR(26),nullzero,notnull"`                // ID of the client who owns this token | ||||||
|  | 	UserID              string    `validate:"required,ulid" bun:"type:CHAR(26),nullzero,notnull"`                // ID of the user who owns this token | ||||||
|  | 	RedirectURI         string    `validate:"required,uri" bun:",nullzero,notnull"`                              // Oauth redirect URI for this token | ||||||
|  | 	Scope               string    `validate:"required" bun:",nullzero,notnull"`                                  // Oauth scope | ||||||
|  | 	Code                string    `validate:"-" bun:",pk,nullzero,notnull,default:''"`                           // Code, if present | ||||||
|  | 	CodeChallenge       string    `validate:"-" bun:",nullzero"`                                                 // Code challenge, if code present | ||||||
|  | 	CodeChallengeMethod string    `validate:"-" bun:",nullzero"`                                                 // Code challenge method, if code present | ||||||
|  | 	CodeCreateAt        time.Time `validate:"required_with=Code" bun:"type:timestamp,nullzero"`                  // Code created time, if code present | ||||||
|  | 	CodeExpiresAt       time.Time `validate:"-" bun:"type:timestamp,nullzero"`                                   // Code expires at -- null means the code never expires | ||||||
|  | 	Access              string    `validate:"-" bun:",pk,nullzero,notnull,default:''"`                           // User level access token, if present | ||||||
|  | 	AccessCreateAt      time.Time `validate:"required_with=Access" bun:"type:timestamp,nullzero"`                // User level access token created time, if access present | ||||||
|  | 	AccessExpiresAt     time.Time `validate:"-" bun:"type:timestamp,nullzero"`                                   // User level access token expires at -- null means the token never expires | ||||||
|  | 	Refresh             string    `validate:"-" bun:",pk,nullzero,notnull,default:''"`                           // Refresh token, if present | ||||||
|  | 	RefreshCreateAt     time.Time `validate:"required_with=Refresh" bun:"type:timestamp,nullzero"`               // Refresh created at, if refresh present | ||||||
|  | 	RefreshExpiresAt    time.Time `validate:"-" bun:"type:timestamp,nullzero"`                                   // Refresh expires at -- null means the refresh token never expires | ||||||
|  | } | ||||||
|  | @ -26,97 +26,34 @@ import ( | ||||||
| // User represents an actual human user of gotosocial. Note, this is a LOCAL gotosocial user, not a remote account. | // User represents an actual human user of gotosocial. Note, this is a LOCAL gotosocial user, not a remote account. | ||||||
| // To cross reference this local user with their account (which can be local or remote), use the AccountID field. | // To cross reference this local user with their account (which can be local or remote), use the AccountID field. | ||||||
| type User struct { | type User struct { | ||||||
| 	/* | 	ID                     string       `validate:"required,ulid" bun:"type:CHAR(26),pk,nullzero,notnull,unique"`      // id of this item in the database | ||||||
| 		BASIC INFO | 	CreatedAt              time.Time    `validate:"-" bun:"type:timestamp,nullzero,notnull,default:current_timestamp"` // when was item created | ||||||
| 	*/ | 	UpdatedAt              time.Time    `validate:"-" bun:"type:timestamp,nullzero,notnull,default:current_timestamp"` // when was item last updated | ||||||
| 
 | 	Email                  string       `validate:"required_with=ConfirmedAt" bun:",nullzero,unique"`                  // confirmed email address for this user, this should be unique -- only one email address registered per instance, multiple users per email are not supported | ||||||
| 	// id of this user in the local database; the end-user will never need to know this, it's strictly internal | 	AccountID              string       `validate:"required,ulid" bun:"type:CHAR(26),nullzero,notnull,unique"`         // The id of the local gtsmodel.Account entry for this user. | ||||||
| 	ID string `bun:"type:CHAR(26),pk,notnull,unique"` | 	Account                *Account     `validate:"-" bun:"rel:belongs-to"`                                            // Pointer to the account of this user that corresponds to AccountID. | ||||||
| 	// confirmed email address for this user, this should be unique -- only one email address registered per instance, multiple users per email are not supported | 	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. | ||||||
| 	Email string `bun:"default:null,unique,nullzero"` | 	SignUpIP               net.IP       `validate:"-" bun:",nullzero"`                                                 // From what IP was this user created? | ||||||
| 	// The id of the local gtsmodel.Account entry for this user, if it exists (unconfirmed users don't have an account yet) | 	CurrentSignInAt        time.Time    `validate:"-" bun:"type:timestamp,nullzero"`                                   // When did the user sign in with their current session. | ||||||
| 	AccountID string   `bun:"type:CHAR(26),unique,nullzero"` | 	CurrentSignInIP        net.IP       `validate:"-" bun:",nullzero"`                                                 // What's the most recent IP of this user | ||||||
| 	Account   *Account `bun:"rel:belongs-to"` | 	LastSignInAt           time.Time    `validate:"-" bun:"type:timestamp,nullzero"`                                   // When did this user last sign in? | ||||||
| 	// 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 | 	LastSignInIP           net.IP       `validate:"-" bun:",nullzero"`                                                 // What's the previous IP of this user? | ||||||
| 	EncryptedPassword string `bun:",notnull"` | 	SignInCount            int          `validate:"min=0" bun:",nullzero,notnull,default:0"`                           // How many times has this user signed in? | ||||||
| 
 | 	InviteID               string       `validate:"omitempty,ulid" bun:"type:CHAR(26),nullzero"`                       // id of the user who invited this user (who let this joker in?) | ||||||
| 	/* | 	ChosenLanguages        []string     `validate:"-" bun:",nullzero"`                                                 // What languages does this user want to see? | ||||||
| 		USER METADATA | 	FilteredLanguages      []string     `validate:"-" bun:",nullzero"`                                                 // What languages does this user not want to see? | ||||||
| 	*/ | 	Locale                 string       `validate:"-" bun:",nullzero"`                                                 // In what timezone/locale is this user located? | ||||||
| 
 | 	CreatedByApplicationID string       `validate:"omitempty,ulid" bun:"type:CHAR(26),nullzero"`                       // Which application id created this user? See gtsmodel.Application | ||||||
| 	// When was this user created? | 	CreatedByApplication   *Application `validate:"-" bun:"rel:belongs-to"`                                            // Pointer to the application corresponding to createdbyapplicationID. | ||||||
| 	CreatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"` | 	LastEmailedAt          time.Time    `validate:"-" bun:"type:timestamp,nullzero"`                                   // When was this user last contacted by email. | ||||||
| 	// From what IP was this user created? | 	ConfirmationToken      string       `validate:"required_with=ConfirmationSentAt" bun:",nullzero"`                  // What confirmation token did we send this user/what are we expecting back? | ||||||
| 	SignUpIP net.IP `bun:",nullzero"` | 	ConfirmationSentAt     time.Time    `validate:"required_with=ConfirmationToken" bun:"type:timestamp,nullzero"`     // When did we send email confirmation to this user? | ||||||
| 	// When was this user updated (eg., password changed, email address changed)? | 	ConfirmedAt            time.Time    `validate:"required_with=Email" bun:"type:timestamp,nullzero"`                 // When did the user confirm their email address | ||||||
| 	UpdatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"` | 	UnconfirmedEmail       string       `validate:"required_without=Email" bun:",nullzero"`                            // Email address that hasn't yet been confirmed | ||||||
| 	// When did this user sign in for their current session? | 	Moderator              bool         `validate:"-" bun:",notnull,default:false"`                                    // Is this user a moderator? | ||||||
| 	CurrentSignInAt time.Time `bun:",nullzero"` | 	Admin                  bool         `validate:"-" bun:",notnull,default:false"`                                    // Is this user an admin? | ||||||
| 	// What's the most recent IP of this user | 	Disabled               bool         `validate:"-" bun:",notnull,default:false"`                                    // Is this user disabled from posting? | ||||||
| 	CurrentSignInIP net.IP `bun:",nullzero"` | 	Approved               bool         `validate:"-" bun:",notnull,default:false"`                                    // Has this user been approved by a moderator? | ||||||
| 	// When did this user last sign in? | 	ResetPasswordToken     string       `validate:"required_with=ResetPasswordSentAt" bun:",nullzero"`                 // The generated token that the user can use to reset their password | ||||||
| 	LastSignInAt time.Time `bun:",nullzero"` | 	ResetPasswordSentAt    time.Time    `validate:"required_with=ResetPasswordToken" bun:"type:timestamp,nullzero"`    // When did we email the user their reset-password email? | ||||||
| 	// What's the previous IP of this user? |  | ||||||
| 	LastSignInIP net.IP `bun:",nullzero"` |  | ||||||
| 	// How many times has this user signed in? |  | ||||||
| 	SignInCount int |  | ||||||
| 	// id of the user who invited this user (who let this guy in?) |  | ||||||
| 	InviteID string `bun:"type:CHAR(26),nullzero"` |  | ||||||
| 	// What languages does this user want to see? |  | ||||||
| 	ChosenLanguages []string |  | ||||||
| 	// What languages does this user not want to see? |  | ||||||
| 	FilteredLanguages []string |  | ||||||
| 	// In what timezone/locale is this user located? |  | ||||||
| 	Locale string `bun:",nullzero"` |  | ||||||
| 	// Which application id created this user? See gtsmodel.Application |  | ||||||
| 	CreatedByApplicationID string       `bun:"type:CHAR(26),nullzero"` |  | ||||||
| 	CreatedByApplication   *Application `bun:"rel:belongs-to"` |  | ||||||
| 	// When did we last contact this user |  | ||||||
| 	LastEmailedAt time.Time `bun:",nullzero"` |  | ||||||
| 
 |  | ||||||
| 	/* |  | ||||||
| 		USER CONFIRMATION |  | ||||||
| 	*/ |  | ||||||
| 
 |  | ||||||
| 	// What confirmation token did we send this user/what are we expecting back? |  | ||||||
| 	ConfirmationToken string `bun:",nullzero"` |  | ||||||
| 	// When did the user confirm their email address |  | ||||||
| 	ConfirmedAt time.Time `bun:",nullzero"` |  | ||||||
| 	// When did we send email confirmation to this user? |  | ||||||
| 	ConfirmationSentAt time.Time `bun:",nullzero"` |  | ||||||
| 	// Email address that hasn't yet been confirmed |  | ||||||
| 	UnconfirmedEmail string `bun:",nullzero"` |  | ||||||
| 
 |  | ||||||
| 	/* |  | ||||||
| 		ACL FLAGS |  | ||||||
| 	*/ |  | ||||||
| 
 |  | ||||||
| 	// Is this user a moderator? |  | ||||||
| 	Moderator bool |  | ||||||
| 	// Is this user an admin? |  | ||||||
| 	Admin bool |  | ||||||
| 	// Is this user disabled from posting? |  | ||||||
| 	Disabled bool |  | ||||||
| 	// Has this user been approved by a moderator? |  | ||||||
| 	Approved bool |  | ||||||
| 
 |  | ||||||
| 	/* |  | ||||||
| 		USER SECURITY |  | ||||||
| 	*/ |  | ||||||
| 
 |  | ||||||
| 	// The generated token that the user can use to reset their password |  | ||||||
| 	ResetPasswordToken string `bun:",nullzero"` |  | ||||||
| 	// When did we email the user their reset-password email? |  | ||||||
| 	ResetPasswordSentAt time.Time `bun:",nullzero"` |  | ||||||
| 
 |  | ||||||
| 	EncryptedOTPSecret     string `bun:",nullzero"` |  | ||||||
| 	EncryptedOTPSecretIv   string `bun:",nullzero"` |  | ||||||
| 	EncryptedOTPSecretSalt string `bun:",nullzero"` |  | ||||||
| 	OTPRequiredForLogin    bool |  | ||||||
| 	OTPBackupCodes         []string |  | ||||||
| 	ConsumedTimestamp      int |  | ||||||
| 	RememberToken          string    `bun:",nullzero"` |  | ||||||
| 	SignInToken            string    `bun:",nullzero"` |  | ||||||
| 	SignInTokenSentAt      time.Time `bun:",nullzero"` |  | ||||||
| 	WebauthnID             string    `bun:",nullzero"` |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -10,6 +10,9 @@ import ( | ||||||
| 
 | 
 | ||||||
| const randomRange = 631152381 // ~20 years in seconds | const randomRange = 631152381 // ~20 years in seconds | ||||||
| 
 | 
 | ||||||
|  | // ULID represents a Universally Unique Lexicographically Sortable Identifier of 26 characters. See https://github.com/oklog/ulid | ||||||
|  | type ULID string | ||||||
|  | 
 | ||||||
| // NewULID returns a new ULID string using the current time, or an error if something goes wrong. | // NewULID returns a new ULID string using the current time, or an error if something goes wrong. | ||||||
| func NewULID() (string, error) { | func NewULID() (string, error) { | ||||||
| 	newUlid, err := ulid.New(ulid.Timestamp(time.Now()), rand.Reader) | 	newUlid, err := ulid.New(ulid.Timestamp(time.Now()), rand.Reader) | ||||||
|  |  | ||||||
|  | @ -16,21 +16,23 @@ | ||||||
|    along with this program.  If not, see <http://www.gnu.org/licenses/>. |    along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
| */ | */ | ||||||
| 
 | 
 | ||||||
| package gtsmodel | package messages | ||||||
| 
 | 
 | ||||||
| // FromClientAPI wraps a message that travels from client API into the processor | import "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
|  | 
 | ||||||
|  | // FromClientAPI wraps a message that travels from the client API into the processor. | ||||||
| type FromClientAPI struct { | type FromClientAPI struct { | ||||||
| 	APObjectType   string | 	APObjectType   string | ||||||
| 	APActivityType string | 	APActivityType string | ||||||
| 	GTSModel       interface{} | 	GTSModel       interface{} | ||||||
| 	OriginAccount  *Account | 	OriginAccount  *gtsmodel.Account | ||||||
| 	TargetAccount  *Account | 	TargetAccount  *gtsmodel.Account | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // FromFederator wraps a message that travels from the federator into the processor | // FromFederator wraps a message that travels from the federator into the processor. | ||||||
| type FromFederator struct { | type FromFederator struct { | ||||||
| 	APObjectType     string | 	APObjectType     string | ||||||
| 	APActivityType   string | 	APActivityType   string | ||||||
| 	GTSModel         interface{} | 	GTSModel         interface{} | ||||||
| 	ReceivingAccount *Account | 	ReceivingAccount *gtsmodel.Account | ||||||
| } | } | ||||||
|  | @ -22,6 +22,7 @@ import ( | ||||||
| 	"context" | 	"context" | ||||||
| 
 | 
 | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" | 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
| 	"github.com/superseriousbusiness/oauth2/v4" | 	"github.com/superseriousbusiness/oauth2/v4" | ||||||
| 	"github.com/superseriousbusiness/oauth2/v4/models" | 	"github.com/superseriousbusiness/oauth2/v4/models" | ||||||
| ) | ) | ||||||
|  | @ -39,7 +40,7 @@ func NewClientStore(db db.Basic) oauth2.ClientStore { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (cs *clientStore) GetByID(ctx context.Context, clientID string) (oauth2.ClientInfo, error) { | func (cs *clientStore) GetByID(ctx context.Context, clientID string) (oauth2.ClientInfo, error) { | ||||||
| 	poc := &Client{} | 	poc := >smodel.Client{} | ||||||
| 	if err := cs.db.GetByID(ctx, clientID, poc); err != nil { | 	if err := cs.db.GetByID(ctx, clientID, poc); err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  | @ -47,7 +48,7 @@ func (cs *clientStore) GetByID(ctx context.Context, clientID string) (oauth2.Cli | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (cs *clientStore) Set(ctx context.Context, id string, cli oauth2.ClientInfo) error { | func (cs *clientStore) Set(ctx context.Context, id string, cli oauth2.ClientInfo) error { | ||||||
| 	poc := &Client{ | 	poc := >smodel.Client{ | ||||||
| 		ID:     cli.GetID(), | 		ID:     cli.GetID(), | ||||||
| 		Secret: cli.GetSecret(), | 		Secret: cli.GetSecret(), | ||||||
| 		Domain: cli.GetDomain(), | 		Domain: cli.GetDomain(), | ||||||
|  | @ -57,16 +58,8 @@ func (cs *clientStore) Set(ctx context.Context, id string, cli oauth2.ClientInfo | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (cs *clientStore) Delete(ctx context.Context, id string) error { | func (cs *clientStore) Delete(ctx context.Context, id string) error { | ||||||
| 	poc := &Client{ | 	poc := >smodel.Client{ | ||||||
| 		ID: id, | 		ID: id, | ||||||
| 	} | 	} | ||||||
| 	return cs.db.DeleteByID(ctx, id, poc) | 	return cs.db.DeleteByID(ctx, id, poc) | ||||||
| } | } | ||||||
| 
 |  | ||||||
| // Client is a handy little wrapper for typical oauth client details |  | ||||||
| type Client struct { |  | ||||||
| 	ID     string `bun:"type:CHAR(26),pk,notnull"` |  | ||||||
| 	Secret string |  | ||||||
| 	Domain string |  | ||||||
| 	UserID string |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  | @ -42,9 +42,9 @@ const () | ||||||
| // SetupSuite sets some variables on the suite that we can use as consts (more or less) throughout | // SetupSuite sets some variables on the suite that we can use as consts (more or less) throughout | ||||||
| func (suite *PgClientStoreTestSuite) SetupSuite() { | func (suite *PgClientStoreTestSuite) SetupSuite() { | ||||||
| 	suite.testClientID = "01FCVB74EW6YBYAEY7QG9CQQF6" | 	suite.testClientID = "01FCVB74EW6YBYAEY7QG9CQQF6" | ||||||
| 	suite.testClientSecret = "test-client-secret" | 	suite.testClientSecret = "4cc87402-259b-4a35-9485-2c8bf54f3763" | ||||||
| 	suite.testClientDomain = "https://example.org" | 	suite.testClientDomain = "https://example.org" | ||||||
| 	suite.testClientUserID = "test-client-user-id" | 	suite.testClientUserID = "01FEGYXKVCDB731QF9MVFXA4F5" | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // SetupTest creates a postgres connection and creates the oauth_clients table before each test | // SetupTest creates a postgres connection and creates the oauth_clients table before each test | ||||||
|  |  | ||||||
|  | @ -26,6 +26,7 @@ import ( | ||||||
| 
 | 
 | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" | 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/id" | 	"github.com/superseriousbusiness/gotosocial/internal/id" | ||||||
| 	"github.com/superseriousbusiness/oauth2/v4" | 	"github.com/superseriousbusiness/oauth2/v4" | ||||||
| 	"github.com/superseriousbusiness/oauth2/v4/models" | 	"github.com/superseriousbusiness/oauth2/v4/models" | ||||||
|  | @ -71,7 +72,7 @@ func newTokenStore(ctx context.Context, db db.Basic, log *logrus.Logger) oauth2. | ||||||
| func (ts *tokenStore) sweep(ctx context.Context) error { | func (ts *tokenStore) sweep(ctx context.Context) error { | ||||||
| 	// select *all* tokens from the db | 	// select *all* tokens from the db | ||||||
| 	// todo: if this becomes expensive (ie., there are fucking LOADS of tokens) then figure out a better way. | 	// todo: if this becomes expensive (ie., there are fucking LOADS of tokens) then figure out a better way. | ||||||
| 	tokens := new([]*Token) | 	tokens := new([]*gtsmodel.Token) | ||||||
| 	if err := ts.db.GetAll(ctx, tokens); err != nil { | 	if err := ts.db.GetAll(ctx, tokens); err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|  | @ -117,17 +118,17 @@ func (ts *tokenStore) Create(ctx context.Context, info oauth2.TokenInfo) error { | ||||||
| 
 | 
 | ||||||
| // RemoveByCode deletes a token from the DB based on the Code field | // RemoveByCode deletes a token from the DB based on the Code field | ||||||
| func (ts *tokenStore) RemoveByCode(ctx context.Context, code string) error { | func (ts *tokenStore) RemoveByCode(ctx context.Context, code string) error { | ||||||
| 	return ts.db.DeleteWhere(ctx, []db.Where{{Key: "code", Value: code}}, &Token{}) | 	return ts.db.DeleteWhere(ctx, []db.Where{{Key: "code", Value: code}}, >smodel.Token{}) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // RemoveByAccess deletes a token from the DB based on the Access field | // RemoveByAccess deletes a token from the DB based on the Access field | ||||||
| func (ts *tokenStore) RemoveByAccess(ctx context.Context, access string) error { | func (ts *tokenStore) RemoveByAccess(ctx context.Context, access string) error { | ||||||
| 	return ts.db.DeleteWhere(ctx, []db.Where{{Key: "access", Value: access}}, &Token{}) | 	return ts.db.DeleteWhere(ctx, []db.Where{{Key: "access", Value: access}}, >smodel.Token{}) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // RemoveByRefresh deletes a token from the DB based on the Refresh field | // RemoveByRefresh deletes a token from the DB based on the Refresh field | ||||||
| func (ts *tokenStore) RemoveByRefresh(ctx context.Context, refresh string) error { | func (ts *tokenStore) RemoveByRefresh(ctx context.Context, refresh string) error { | ||||||
| 	return ts.db.DeleteWhere(ctx, []db.Where{{Key: "refresh", Value: refresh}}, &Token{}) | 	return ts.db.DeleteWhere(ctx, []db.Where{{Key: "refresh", Value: refresh}}, >smodel.Token{}) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // GetByCode selects a token from the DB based on the Code field | // GetByCode selects a token from the DB based on the Code field | ||||||
|  | @ -135,7 +136,7 @@ func (ts *tokenStore) GetByCode(ctx context.Context, code string) (oauth2.TokenI | ||||||
| 	if code == "" { | 	if code == "" { | ||||||
| 		return nil, nil | 		return nil, nil | ||||||
| 	} | 	} | ||||||
| 	dbt := &Token{ | 	dbt := >smodel.Token{ | ||||||
| 		Code: code, | 		Code: code, | ||||||
| 	} | 	} | ||||||
| 	if err := ts.db.GetWhere(ctx, []db.Where{{Key: "code", Value: code}}, dbt); err != nil { | 	if err := ts.db.GetWhere(ctx, []db.Where{{Key: "code", Value: code}}, dbt); err != nil { | ||||||
|  | @ -149,7 +150,7 @@ func (ts *tokenStore) GetByAccess(ctx context.Context, access string) (oauth2.To | ||||||
| 	if access == "" { | 	if access == "" { | ||||||
| 		return nil, nil | 		return nil, nil | ||||||
| 	} | 	} | ||||||
| 	dbt := &Token{ | 	dbt := >smodel.Token{ | ||||||
| 		Access: access, | 		Access: access, | ||||||
| 	} | 	} | ||||||
| 	if err := ts.db.GetWhere(ctx, []db.Where{{Key: "access", Value: access}}, dbt); err != nil { | 	if err := ts.db.GetWhere(ctx, []db.Where{{Key: "access", Value: access}}, dbt); err != nil { | ||||||
|  | @ -163,7 +164,7 @@ func (ts *tokenStore) GetByRefresh(ctx context.Context, refresh string) (oauth2. | ||||||
| 	if refresh == "" { | 	if refresh == "" { | ||||||
| 		return nil, nil | 		return nil, nil | ||||||
| 	} | 	} | ||||||
| 	dbt := &Token{ | 	dbt := >smodel.Token{ | ||||||
| 		Refresh: refresh, | 		Refresh: refresh, | ||||||
| 	} | 	} | ||||||
| 	if err := ts.db.GetWhere(ctx, []db.Where{{Key: "refresh", Value: refresh}}, dbt); err != nil { | 	if err := ts.db.GetWhere(ctx, []db.Where{{Key: "refresh", Value: refresh}}, dbt); err != nil { | ||||||
|  | @ -176,37 +177,8 @@ func (ts *tokenStore) GetByRefresh(ctx context.Context, refresh string) (oauth2. | ||||||
| 	The following models are basically helpers for the token store implementation, they should only be used internally. | 	The following models are basically helpers for the token store implementation, they should only be used internally. | ||||||
| */ | */ | ||||||
| 
 | 
 | ||||||
| // Token is a translation of the gotosocial token with the ExpiresIn fields replaced with ExpiresAt. |  | ||||||
| // |  | ||||||
| // Explanation for this: gotosocial assumes an in-memory or file database of some kind, where a time-to-live parameter (TTL) can be defined, |  | ||||||
| // and tokens with expired TTLs are automatically removed. Since some databases don't have that feature, it's easier to set an expiry time and |  | ||||||
| // then periodically sweep out tokens when that time has passed. |  | ||||||
| // |  | ||||||
| // Note that this struct does *not* satisfy the token interface shown here: https://github.com/superseriousbusiness/oauth2/blob/master/model.go#L22 |  | ||||||
| // and implemented here: https://github.com/superseriousbusiness/oauth2/blob/master/models/token.go. |  | ||||||
| // As such, manual translation is always required between Token and the gotosocial *model.Token. The helper functions oauthTokenToPGToken |  | ||||||
| // and pgTokenToOauthToken can be used for that. |  | ||||||
| type Token struct { |  | ||||||
| 	ID                  string `bun:"type:CHAR(26),pk,notnull"` |  | ||||||
| 	ClientID            string |  | ||||||
| 	UserID              string |  | ||||||
| 	RedirectURI         string |  | ||||||
| 	Scope               string |  | ||||||
| 	Code                string `bun:"default:'',pk"` |  | ||||||
| 	CodeChallenge       string |  | ||||||
| 	CodeChallengeMethod string |  | ||||||
| 	CodeCreateAt        time.Time `bun:",nullzero"` |  | ||||||
| 	CodeExpiresAt       time.Time `bun:",nullzero"` |  | ||||||
| 	Access              string    `bun:"default:'',pk"` |  | ||||||
| 	AccessCreateAt      time.Time `bun:",nullzero"` |  | ||||||
| 	AccessExpiresAt     time.Time `bun:",nullzero"` |  | ||||||
| 	Refresh             string    `bun:"default:'',pk"` |  | ||||||
| 	RefreshCreateAt     time.Time `bun:",nullzero"` |  | ||||||
| 	RefreshExpiresAt    time.Time `bun:",nullzero"` |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // TokenToDBToken is a lil util function that takes a gotosocial token and gives back a token for inserting into a database. | // TokenToDBToken is a lil util function that takes a gotosocial token and gives back a token for inserting into a database. | ||||||
| func TokenToDBToken(tkn *models.Token) *Token { | func TokenToDBToken(tkn *models.Token) *gtsmodel.Token { | ||||||
| 	now := time.Now() | 	now := time.Now() | ||||||
| 
 | 
 | ||||||
| 	// For the following, we want to make sure we're not adding a time.Now() to an *empty* ExpiresIn, otherwise that's | 	// For the following, we want to make sure we're not adding a time.Now() to an *empty* ExpiresIn, otherwise that's | ||||||
|  | @ -228,7 +200,7 @@ func TokenToDBToken(tkn *models.Token) *Token { | ||||||
| 		rea = now.Add(tkn.RefreshExpiresIn) | 		rea = now.Add(tkn.RefreshExpiresIn) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return &Token{ | 	return >smodel.Token{ | ||||||
| 		ClientID:            tkn.ClientID, | 		ClientID:            tkn.ClientID, | ||||||
| 		UserID:              tkn.UserID, | 		UserID:              tkn.UserID, | ||||||
| 		RedirectURI:         tkn.RedirectURI, | 		RedirectURI:         tkn.RedirectURI, | ||||||
|  | @ -248,7 +220,7 @@ func TokenToDBToken(tkn *models.Token) *Token { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // DBTokenToToken is a lil util function that takes a database token and gives back a gotosocial token | // DBTokenToToken is a lil util function that takes a database token and gives back a gotosocial token | ||||||
| func DBTokenToToken(dbt *Token) *models.Token { | func DBTokenToToken(dbt *gtsmodel.Token) *models.Token { | ||||||
| 	now := time.Now() | 	now := time.Now() | ||||||
| 
 | 
 | ||||||
| 	var codeExpiresIn time.Duration | 	var codeExpiresIn time.Duration | ||||||
|  |  | ||||||
|  | @ -30,6 +30,7 @@ import ( | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/media" | 	"github.com/superseriousbusiness/gotosocial/internal/media" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/messages" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/oauth" | 	"github.com/superseriousbusiness/gotosocial/internal/oauth" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/typeutils" | 	"github.com/superseriousbusiness/gotosocial/internal/typeutils" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/visibility" | 	"github.com/superseriousbusiness/gotosocial/internal/visibility" | ||||||
|  | @ -79,7 +80,7 @@ type processor struct { | ||||||
| 	tc            typeutils.TypeConverter | 	tc            typeutils.TypeConverter | ||||||
| 	config        *config.Config | 	config        *config.Config | ||||||
| 	mediaHandler  media.Handler | 	mediaHandler  media.Handler | ||||||
| 	fromClientAPI chan gtsmodel.FromClientAPI | 	fromClientAPI chan messages.FromClientAPI | ||||||
| 	oauthServer   oauth.Server | 	oauthServer   oauth.Server | ||||||
| 	filter        visibility.Filter | 	filter        visibility.Filter | ||||||
| 	db            db.DB | 	db            db.DB | ||||||
|  | @ -88,7 +89,7 @@ type processor struct { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // New returns a new account processor. | // New returns a new account processor. | ||||||
| func New(db db.DB, tc typeutils.TypeConverter, mediaHandler media.Handler, oauthServer oauth.Server, fromClientAPI chan gtsmodel.FromClientAPI, federator federation.Federator, config *config.Config, log *logrus.Logger) Processor { | func New(db db.DB, tc typeutils.TypeConverter, mediaHandler media.Handler, oauthServer oauth.Server, fromClientAPI chan messages.FromClientAPI, federator federation.Federator, config *config.Config, log *logrus.Logger) Processor { | ||||||
| 	return &processor{ | 	return &processor{ | ||||||
| 		tc:            tc, | 		tc:            tc, | ||||||
| 		config:        config, | 		config:        config, | ||||||
|  |  | ||||||
|  | @ -22,11 +22,13 @@ import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 
 | 
 | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/ap" | ||||||
| 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" | 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" | 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/id" | 	"github.com/superseriousbusiness/gotosocial/internal/id" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/messages" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/util" | 	"github.com/superseriousbusiness/gotosocial/internal/util" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | @ -111,9 +113,9 @@ func (p *processor) BlockCreate(ctx context.Context, requestingAccount *gtsmodel | ||||||
| 
 | 
 | ||||||
| 	// follow request status changed so send the UNDO activity to the channel for async processing | 	// follow request status changed so send the UNDO activity to the channel for async processing | ||||||
| 	if frChanged { | 	if frChanged { | ||||||
| 		p.fromClientAPI <- gtsmodel.FromClientAPI{ | 		p.fromClientAPI <- messages.FromClientAPI{ | ||||||
| 			APObjectType:   gtsmodel.ActivityStreamsFollow, | 			APObjectType:   ap.ActivityFollow, | ||||||
| 			APActivityType: gtsmodel.ActivityStreamsUndo, | 			APActivityType: ap.ActivityUndo, | ||||||
| 			GTSModel: >smodel.Follow{ | 			GTSModel: >smodel.Follow{ | ||||||
| 				AccountID:       requestingAccount.ID, | 				AccountID:       requestingAccount.ID, | ||||||
| 				TargetAccountID: targetAccountID, | 				TargetAccountID: targetAccountID, | ||||||
|  | @ -126,9 +128,9 @@ func (p *processor) BlockCreate(ctx context.Context, requestingAccount *gtsmodel | ||||||
| 
 | 
 | ||||||
| 	// follow status changed so send the UNDO activity to the channel for async processing | 	// follow status changed so send the UNDO activity to the channel for async processing | ||||||
| 	if fChanged { | 	if fChanged { | ||||||
| 		p.fromClientAPI <- gtsmodel.FromClientAPI{ | 		p.fromClientAPI <- messages.FromClientAPI{ | ||||||
| 			APObjectType:   gtsmodel.ActivityStreamsFollow, | 			APObjectType:   ap.ActivityFollow, | ||||||
| 			APActivityType: gtsmodel.ActivityStreamsUndo, | 			APActivityType: ap.ActivityUndo, | ||||||
| 			GTSModel: >smodel.Follow{ | 			GTSModel: >smodel.Follow{ | ||||||
| 				AccountID:       requestingAccount.ID, | 				AccountID:       requestingAccount.ID, | ||||||
| 				TargetAccountID: targetAccountID, | 				TargetAccountID: targetAccountID, | ||||||
|  | @ -140,9 +142,9 @@ func (p *processor) BlockCreate(ctx context.Context, requestingAccount *gtsmodel | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// handle the rest of the block process asynchronously | 	// handle the rest of the block process asynchronously | ||||||
| 	p.fromClientAPI <- gtsmodel.FromClientAPI{ | 	p.fromClientAPI <- messages.FromClientAPI{ | ||||||
| 		APObjectType:   gtsmodel.ActivityStreamsBlock, | 		APObjectType:   ap.ActivityBlock, | ||||||
| 		APActivityType: gtsmodel.ActivityStreamsCreate, | 		APActivityType: ap.ActivityCreate, | ||||||
| 		GTSModel:       block, | 		GTSModel:       block, | ||||||
| 		OriginAccount:  requestingAccount, | 		OriginAccount:  requestingAccount, | ||||||
| 		TargetAccount:  targetAccount, | 		TargetAccount:  targetAccount, | ||||||
|  |  | ||||||
|  | @ -22,11 +22,13 @@ import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 
 | 
 | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/ap" | ||||||
| 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" | 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" | 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/id" | 	"github.com/superseriousbusiness/gotosocial/internal/id" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/messages" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/util" | 	"github.com/superseriousbusiness/gotosocial/internal/util" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | @ -99,9 +101,9 @@ func (p *processor) FollowCreate(ctx context.Context, requestingAccount *gtsmode | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// otherwise we leave the follow request as it is and we handle the rest of the process asynchronously | 	// otherwise we leave the follow request as it is and we handle the rest of the process asynchronously | ||||||
| 	p.fromClientAPI <- gtsmodel.FromClientAPI{ | 	p.fromClientAPI <- messages.FromClientAPI{ | ||||||
| 		APObjectType:   gtsmodel.ActivityStreamsFollow, | 		APObjectType:   ap.ActivityFollow, | ||||||
| 		APActivityType: gtsmodel.ActivityStreamsCreate, | 		APActivityType: ap.ActivityCreate, | ||||||
| 		GTSModel:       fr, | 		GTSModel:       fr, | ||||||
| 		OriginAccount:  requestingAccount, | 		OriginAccount:  requestingAccount, | ||||||
| 		TargetAccount:  targetAcct, | 		TargetAccount:  targetAcct, | ||||||
|  |  | ||||||
|  | @ -23,9 +23,10 @@ import ( | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/ap" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" | 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/oauth" | 	"github.com/superseriousbusiness/gotosocial/internal/messages" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // Delete handles the complete deletion of an account. | // Delete handles the complete deletion of an account. | ||||||
|  | @ -64,12 +65,12 @@ func (p *processor) Delete(ctx context.Context, account *gtsmodel.Account, origi | ||||||
| 		u := >smodel.User{} | 		u := >smodel.User{} | ||||||
| 		if err := p.db.GetWhere(ctx, []db.Where{{Key: "account_id", Value: account.ID}}, u); err == nil { | 		if err := p.db.GetWhere(ctx, []db.Where{{Key: "account_id", Value: account.ID}}, u); err == nil { | ||||||
| 			// we got one! select all tokens with the user's ID | 			// we got one! select all tokens with the user's ID | ||||||
| 			tokens := []*oauth.Token{} | 			tokens := []*gtsmodel.Token{} | ||||||
| 			if err := p.db.GetWhere(ctx, []db.Where{{Key: "user_id", Value: u.ID}}, &tokens); err == nil { | 			if err := p.db.GetWhere(ctx, []db.Where{{Key: "user_id", Value: u.ID}}, &tokens); err == nil { | ||||||
| 				// we have some tokens to delete | 				// we have some tokens to delete | ||||||
| 				for _, t := range tokens { | 				for _, t := range tokens { | ||||||
| 					// delete client(s) associated with this token | 					// delete client(s) associated with this token | ||||||
| 					if err := p.db.DeleteByID(ctx, t.ClientID, &oauth.Client{}); err != nil { | 					if err := p.db.DeleteByID(ctx, t.ClientID, >smodel.Client{}); err != nil { | ||||||
| 						l.Errorf("error deleting oauth client: %s", err) | 						l.Errorf("error deleting oauth client: %s", err) | ||||||
| 					} | 					} | ||||||
| 					// delete application(s) associated with this token | 					// delete application(s) associated with this token | ||||||
|  | @ -150,9 +151,9 @@ selectStatusesLoop: | ||||||
| 			// pass the status delete through the client api channel for processing | 			// pass the status delete through the client api channel for processing | ||||||
| 			s.Account = account | 			s.Account = account | ||||||
| 			l.Debug("putting status in the client api channel") | 			l.Debug("putting status in the client api channel") | ||||||
| 			p.fromClientAPI <- gtsmodel.FromClientAPI{ | 			p.fromClientAPI <- messages.FromClientAPI{ | ||||||
| 				APObjectType:   gtsmodel.ActivityStreamsNote, | 				APObjectType:   ap.ObjectNote, | ||||||
| 				APActivityType: gtsmodel.ActivityStreamsDelete, | 				APActivityType: ap.ActivityDelete, | ||||||
| 				GTSModel:       s, | 				GTSModel:       s, | ||||||
| 				OriginAccount:  account, | 				OriginAccount:  account, | ||||||
| 				TargetAccount:  account, | 				TargetAccount:  account, | ||||||
|  | @ -186,9 +187,9 @@ selectStatusesLoop: | ||||||
| 				} | 				} | ||||||
| 
 | 
 | ||||||
| 				l.Debug("putting boost undo in the client api channel") | 				l.Debug("putting boost undo in the client api channel") | ||||||
| 				p.fromClientAPI <- gtsmodel.FromClientAPI{ | 				p.fromClientAPI <- messages.FromClientAPI{ | ||||||
| 					APObjectType:   gtsmodel.ActivityStreamsAnnounce, | 					APObjectType:   ap.ActivityAnnounce, | ||||||
| 					APActivityType: gtsmodel.ActivityStreamsUndo, | 					APActivityType: ap.ActivityUndo, | ||||||
| 					GTSModel:       s, | 					GTSModel:       s, | ||||||
| 					OriginAccount:  b.Account, | 					OriginAccount:  b.Account, | ||||||
| 					TargetAccount:  account, | 					TargetAccount:  account, | ||||||
|  |  | ||||||
|  | @ -22,10 +22,12 @@ import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 
 | 
 | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/ap" | ||||||
| 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" | 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" | 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/messages" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func (p *processor) BlockRemove(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccountID string) (*apimodel.Relationship, gtserror.WithCode) { | func (p *processor) BlockRemove(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccountID string) (*apimodel.Relationship, gtserror.WithCode) { | ||||||
|  | @ -52,9 +54,9 @@ func (p *processor) BlockRemove(ctx context.Context, requestingAccount *gtsmodel | ||||||
| 
 | 
 | ||||||
| 	// block status changed so send the UNDO activity to the channel for async processing | 	// block status changed so send the UNDO activity to the channel for async processing | ||||||
| 	if blockChanged { | 	if blockChanged { | ||||||
| 		p.fromClientAPI <- gtsmodel.FromClientAPI{ | 		p.fromClientAPI <- messages.FromClientAPI{ | ||||||
| 			APObjectType:   gtsmodel.ActivityStreamsBlock, | 			APObjectType:   ap.ActivityBlock, | ||||||
| 			APActivityType: gtsmodel.ActivityStreamsUndo, | 			APActivityType: ap.ActivityUndo, | ||||||
| 			GTSModel:       block, | 			GTSModel:       block, | ||||||
| 			OriginAccount:  requestingAccount, | 			OriginAccount:  requestingAccount, | ||||||
| 			TargetAccount:  targetAccount, | 			TargetAccount:  targetAccount, | ||||||
|  |  | ||||||
|  | @ -22,10 +22,12 @@ import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 
 | 
 | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/ap" | ||||||
| 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" | 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" | 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/messages" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func (p *processor) FollowRemove(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccountID string) (*apimodel.Relationship, gtserror.WithCode) { | func (p *processor) FollowRemove(ctx context.Context, requestingAccount *gtsmodel.Account, targetAccountID string) (*apimodel.Relationship, gtserror.WithCode) { | ||||||
|  | @ -78,9 +80,9 @@ func (p *processor) FollowRemove(ctx context.Context, requestingAccount *gtsmode | ||||||
| 
 | 
 | ||||||
| 	// follow request status changed so send the UNDO activity to the channel for async processing | 	// follow request status changed so send the UNDO activity to the channel for async processing | ||||||
| 	if frChanged { | 	if frChanged { | ||||||
| 		p.fromClientAPI <- gtsmodel.FromClientAPI{ | 		p.fromClientAPI <- messages.FromClientAPI{ | ||||||
| 			APObjectType:   gtsmodel.ActivityStreamsFollow, | 			APObjectType:   ap.ActivityFollow, | ||||||
| 			APActivityType: gtsmodel.ActivityStreamsUndo, | 			APActivityType: ap.ActivityUndo, | ||||||
| 			GTSModel: >smodel.Follow{ | 			GTSModel: >smodel.Follow{ | ||||||
| 				AccountID:       requestingAccount.ID, | 				AccountID:       requestingAccount.ID, | ||||||
| 				TargetAccountID: targetAccountID, | 				TargetAccountID: targetAccountID, | ||||||
|  | @ -93,9 +95,9 @@ func (p *processor) FollowRemove(ctx context.Context, requestingAccount *gtsmode | ||||||
| 
 | 
 | ||||||
| 	// follow status changed so send the UNDO activity to the channel for async processing | 	// follow status changed so send the UNDO activity to the channel for async processing | ||||||
| 	if fChanged { | 	if fChanged { | ||||||
| 		p.fromClientAPI <- gtsmodel.FromClientAPI{ | 		p.fromClientAPI <- messages.FromClientAPI{ | ||||||
| 			APObjectType:   gtsmodel.ActivityStreamsFollow, | 			APObjectType:   ap.ActivityFollow, | ||||||
| 			APActivityType: gtsmodel.ActivityStreamsUndo, | 			APActivityType: ap.ActivityUndo, | ||||||
| 			GTSModel: >smodel.Follow{ | 			GTSModel: >smodel.Follow{ | ||||||
| 				AccountID:       requestingAccount.ID, | 				AccountID:       requestingAccount.ID, | ||||||
| 				TargetAccountID: targetAccountID, | 				TargetAccountID: targetAccountID, | ||||||
|  |  | ||||||
|  | @ -26,11 +26,13 @@ import ( | ||||||
| 	"io" | 	"io" | ||||||
| 	"mime/multipart" | 	"mime/multipart" | ||||||
| 
 | 
 | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/ap" | ||||||
| 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" | 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/media" | 	"github.com/superseriousbusiness/gotosocial/internal/media" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/messages" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/text" | 	"github.com/superseriousbusiness/gotosocial/internal/text" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/util" | 	"github.com/superseriousbusiness/gotosocial/internal/validate" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func (p *processor) Update(ctx context.Context, account *gtsmodel.Account, form *apimodel.UpdateCredentialsRequest) (*apimodel.Account, error) { | func (p *processor) Update(ctx context.Context, account *gtsmodel.Account, form *apimodel.UpdateCredentialsRequest) (*apimodel.Account, error) { | ||||||
|  | @ -49,7 +51,7 @@ func (p *processor) Update(ctx context.Context, account *gtsmodel.Account, form | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if form.DisplayName != nil { | 	if form.DisplayName != nil { | ||||||
| 		if err := util.ValidateDisplayName(*form.DisplayName); err != nil { | 		if err := validate.DisplayName(*form.DisplayName); err != nil { | ||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
| 		displayName := text.RemoveHTML(*form.DisplayName) // no html allowed in display name | 		displayName := text.RemoveHTML(*form.DisplayName) // no html allowed in display name | ||||||
|  | @ -59,7 +61,7 @@ func (p *processor) Update(ctx context.Context, account *gtsmodel.Account, form | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if form.Note != nil { | 	if form.Note != nil { | ||||||
| 		if err := util.ValidateNote(*form.Note); err != nil { | 		if err := validate.Note(*form.Note); err != nil { | ||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
| 		note := text.SanitizeHTML(*form.Note) // html OK in note but sanitize it | 		note := text.SanitizeHTML(*form.Note) // html OK in note but sanitize it | ||||||
|  | @ -92,7 +94,7 @@ func (p *processor) Update(ctx context.Context, account *gtsmodel.Account, form | ||||||
| 
 | 
 | ||||||
| 	if form.Source != nil { | 	if form.Source != nil { | ||||||
| 		if form.Source.Language != nil { | 		if form.Source.Language != nil { | ||||||
| 			if err := util.ValidateLanguage(*form.Source.Language); err != nil { | 			if err := validate.Language(*form.Source.Language); err != nil { | ||||||
| 				return nil, err | 				return nil, err | ||||||
| 			} | 			} | ||||||
| 			if err := p.db.UpdateOneByID(ctx, account.ID, "language", *form.Source.Language, >smodel.Account{}); err != nil { | 			if err := p.db.UpdateOneByID(ctx, account.ID, "language", *form.Source.Language, >smodel.Account{}); err != nil { | ||||||
|  | @ -107,7 +109,7 @@ func (p *processor) Update(ctx context.Context, account *gtsmodel.Account, form | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		if form.Source.Privacy != nil { | 		if form.Source.Privacy != nil { | ||||||
| 			if err := util.ValidatePrivacy(*form.Source.Privacy); err != nil { | 			if err := validate.Privacy(*form.Source.Privacy); err != nil { | ||||||
| 				return nil, err | 				return nil, err | ||||||
| 			} | 			} | ||||||
| 			if err := p.db.UpdateOneByID(ctx, account.ID, "privacy", *form.Source.Privacy, >smodel.Account{}); err != nil { | 			if err := p.db.UpdateOneByID(ctx, account.ID, "privacy", *form.Source.Privacy, >smodel.Account{}); err != nil { | ||||||
|  | @ -122,9 +124,9 @@ func (p *processor) Update(ctx context.Context, account *gtsmodel.Account, form | ||||||
| 		return nil, fmt.Errorf("could not fetch updated account %s: %s", account.ID, err) | 		return nil, fmt.Errorf("could not fetch updated account %s: %s", account.ID, err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	p.fromClientAPI <- gtsmodel.FromClientAPI{ | 	p.fromClientAPI <- messages.FromClientAPI{ | ||||||
| 		APObjectType:   gtsmodel.ActivityStreamsProfile, | 		APObjectType:   ap.ObjectProfile, | ||||||
| 		APActivityType: gtsmodel.ActivityStreamsUpdate, | 		APActivityType: ap.ActivityUpdate, | ||||||
| 		GTSModel:       updatedAccount, | 		GTSModel:       updatedAccount, | ||||||
| 		OriginAccount:  updatedAccount, | 		OriginAccount:  updatedAccount, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -29,6 +29,7 @@ import ( | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/media" | 	"github.com/superseriousbusiness/gotosocial/internal/media" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/messages" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/typeutils" | 	"github.com/superseriousbusiness/gotosocial/internal/typeutils" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | @ -46,13 +47,13 @@ type processor struct { | ||||||
| 	tc            typeutils.TypeConverter | 	tc            typeutils.TypeConverter | ||||||
| 	config        *config.Config | 	config        *config.Config | ||||||
| 	mediaHandler  media.Handler | 	mediaHandler  media.Handler | ||||||
| 	fromClientAPI chan gtsmodel.FromClientAPI | 	fromClientAPI chan messages.FromClientAPI | ||||||
| 	db            db.DB | 	db            db.DB | ||||||
| 	log           *logrus.Logger | 	log           *logrus.Logger | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // New returns a new admin processor. | // New returns a new admin processor. | ||||||
| func New(db db.DB, tc typeutils.TypeConverter, mediaHandler media.Handler, fromClientAPI chan gtsmodel.FromClientAPI, config *config.Config, log *logrus.Logger) Processor { | func New(db db.DB, tc typeutils.TypeConverter, mediaHandler media.Handler, fromClientAPI chan messages.FromClientAPI, config *config.Config, log *logrus.Logger) Processor { | ||||||
| 	return &processor{ | 	return &processor{ | ||||||
| 		tc:            tc, | 		tc:            tc, | ||||||
| 		config:        config, | 		config:        config, | ||||||
|  |  | ||||||
|  | @ -24,11 +24,13 @@ import ( | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/ap" | ||||||
| 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" | 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" | 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/id" | 	"github.com/superseriousbusiness/gotosocial/internal/id" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/messages" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/text" | 	"github.com/superseriousbusiness/gotosocial/internal/text" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | @ -140,9 +142,9 @@ selectAccountsLoop: | ||||||
| 			l.Debugf("putting delete for account %s in the clientAPI channel", a.Username) | 			l.Debugf("putting delete for account %s in the clientAPI channel", a.Username) | ||||||
| 
 | 
 | ||||||
| 			// pass the account delete through the client api channel for processing | 			// pass the account delete through the client api channel for processing | ||||||
| 			p.fromClientAPI <- gtsmodel.FromClientAPI{ | 			p.fromClientAPI <- messages.FromClientAPI{ | ||||||
| 				APObjectType:   gtsmodel.ActivityStreamsPerson, | 				APObjectType:   ap.ActorPerson, | ||||||
| 				APActivityType: gtsmodel.ActivityStreamsDelete, | 				APActivityType: ap.ActivityDelete, | ||||||
| 				GTSModel:       block, | 				GTSModel:       block, | ||||||
| 				OriginAccount:  account, | 				OriginAccount:  account, | ||||||
| 				TargetAccount:  a, | 				TargetAccount:  a, | ||||||
|  |  | ||||||
|  | @ -43,7 +43,6 @@ func (p *processor) AppCreate(ctx context.Context, authed *oauth.Auth, form *api | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 	clientSecret := uuid.NewString() | 	clientSecret := uuid.NewString() | ||||||
| 	vapidKey := uuid.NewString() |  | ||||||
| 
 | 
 | ||||||
| 	appID, err := id.NewRandomULID() | 	appID, err := id.NewRandomULID() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  | @ -59,7 +58,6 @@ func (p *processor) AppCreate(ctx context.Context, authed *oauth.Auth, form *api | ||||||
| 		ClientID:     clientID, | 		ClientID:     clientID, | ||||||
| 		ClientSecret: clientSecret, | 		ClientSecret: clientSecret, | ||||||
| 		Scopes:       scopes, | 		Scopes:       scopes, | ||||||
| 		VapidKey:     vapidKey, |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// chuck it in the db | 	// chuck it in the db | ||||||
|  | @ -68,7 +66,7 @@ func (p *processor) AppCreate(ctx context.Context, authed *oauth.Auth, form *api | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// now we need to model an oauth client from the application that the oauth library can use | 	// now we need to model an oauth client from the application that the oauth library can use | ||||||
| 	oc := &oauth.Client{ | 	oc := >smodel.Client{ | ||||||
| 		ID:     clientID, | 		ID:     clientID, | ||||||
| 		Secret: clientSecret, | 		Secret: clientSecret, | ||||||
| 		Domain: form.RedirectURIs, | 		Domain: form.RedirectURIs, | ||||||
|  |  | ||||||
|  | @ -21,10 +21,11 @@ package processing | ||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
| 
 | 
 | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/ap" | ||||||
| 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" | 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" | 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/messages" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/oauth" | 	"github.com/superseriousbusiness/gotosocial/internal/oauth" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | @ -77,9 +78,9 @@ func (p *processor) FollowRequestAccept(ctx context.Context, auth *oauth.Auth, a | ||||||
| 		follow.TargetAccount = followTargetAccount | 		follow.TargetAccount = followTargetAccount | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	p.fromClientAPI <- gtsmodel.FromClientAPI{ | 	p.fromClientAPI <- messages.FromClientAPI{ | ||||||
| 		APObjectType:   gtsmodel.ActivityStreamsFollow, | 		APObjectType:   ap.ActivityFollow, | ||||||
| 		APActivityType: gtsmodel.ActivityStreamsAccept, | 		APActivityType: ap.ActivityAccept, | ||||||
| 		GTSModel:       follow, | 		GTSModel:       follow, | ||||||
| 		OriginAccount:  follow.Account, | 		OriginAccount:  follow.Account, | ||||||
| 		TargetAccount:  follow.TargetAccount, | 		TargetAccount:  follow.TargetAccount, | ||||||
|  |  | ||||||
|  | @ -25,16 +25,18 @@ import ( | ||||||
| 	"net/url" | 	"net/url" | ||||||
| 
 | 
 | ||||||
| 	"github.com/go-fed/activity/streams" | 	"github.com/go-fed/activity/streams" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/ap" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" | 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/messages" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func (p *processor) processFromClientAPI(ctx context.Context, clientMsg gtsmodel.FromClientAPI) error { | func (p *processor) processFromClientAPI(ctx context.Context, clientMsg messages.FromClientAPI) error { | ||||||
| 	switch clientMsg.APActivityType { | 	switch clientMsg.APActivityType { | ||||||
| 	case gtsmodel.ActivityStreamsCreate: | 	case ap.ActivityCreate: | ||||||
| 		// CREATE | 		// CREATE | ||||||
| 		switch clientMsg.APObjectType { | 		switch clientMsg.APObjectType { | ||||||
| 		case gtsmodel.ActivityStreamsNote: | 		case ap.ObjectNote: | ||||||
| 			// CREATE NOTE | 			// CREATE NOTE | ||||||
| 			status, ok := clientMsg.GTSModel.(*gtsmodel.Status) | 			status, ok := clientMsg.GTSModel.(*gtsmodel.Status) | ||||||
| 			if !ok { | 			if !ok { | ||||||
|  | @ -49,10 +51,10 @@ func (p *processor) processFromClientAPI(ctx context.Context, clientMsg gtsmodel | ||||||
| 				return err | 				return err | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			if status.VisibilityAdvanced != nil && status.VisibilityAdvanced.Federated { | 			if status.VisibilityAdvanced.Federated { | ||||||
| 				return p.federateStatus(ctx, status) | 				return p.federateStatus(ctx, status) | ||||||
| 			} | 			} | ||||||
| 		case gtsmodel.ActivityStreamsFollow: | 		case ap.ActivityFollow: | ||||||
| 			// CREATE FOLLOW REQUEST | 			// CREATE FOLLOW REQUEST | ||||||
| 			followRequest, ok := clientMsg.GTSModel.(*gtsmodel.FollowRequest) | 			followRequest, ok := clientMsg.GTSModel.(*gtsmodel.FollowRequest) | ||||||
| 			if !ok { | 			if !ok { | ||||||
|  | @ -64,7 +66,7 @@ func (p *processor) processFromClientAPI(ctx context.Context, clientMsg gtsmodel | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			return p.federateFollow(ctx, followRequest, clientMsg.OriginAccount, clientMsg.TargetAccount) | 			return p.federateFollow(ctx, followRequest, clientMsg.OriginAccount, clientMsg.TargetAccount) | ||||||
| 		case gtsmodel.ActivityStreamsLike: | 		case ap.ActivityLike: | ||||||
| 			// CREATE LIKE/FAVE | 			// CREATE LIKE/FAVE | ||||||
| 			fave, ok := clientMsg.GTSModel.(*gtsmodel.StatusFave) | 			fave, ok := clientMsg.GTSModel.(*gtsmodel.StatusFave) | ||||||
| 			if !ok { | 			if !ok { | ||||||
|  | @ -76,7 +78,7 @@ func (p *processor) processFromClientAPI(ctx context.Context, clientMsg gtsmodel | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			return p.federateFave(ctx, fave, clientMsg.OriginAccount, clientMsg.TargetAccount) | 			return p.federateFave(ctx, fave, clientMsg.OriginAccount, clientMsg.TargetAccount) | ||||||
| 		case gtsmodel.ActivityStreamsAnnounce: | 		case ap.ActivityAnnounce: | ||||||
| 			// CREATE BOOST/ANNOUNCE | 			// CREATE BOOST/ANNOUNCE | ||||||
| 			boostWrapperStatus, ok := clientMsg.GTSModel.(*gtsmodel.Status) | 			boostWrapperStatus, ok := clientMsg.GTSModel.(*gtsmodel.Status) | ||||||
| 			if !ok { | 			if !ok { | ||||||
|  | @ -92,7 +94,7 @@ func (p *processor) processFromClientAPI(ctx context.Context, clientMsg gtsmodel | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			return p.federateAnnounce(ctx, boostWrapperStatus, clientMsg.OriginAccount, clientMsg.TargetAccount) | 			return p.federateAnnounce(ctx, boostWrapperStatus, clientMsg.OriginAccount, clientMsg.TargetAccount) | ||||||
| 		case gtsmodel.ActivityStreamsBlock: | 		case ap.ActivityBlock: | ||||||
| 			// CREATE BLOCK | 			// CREATE BLOCK | ||||||
| 			block, ok := clientMsg.GTSModel.(*gtsmodel.Block) | 			block, ok := clientMsg.GTSModel.(*gtsmodel.Block) | ||||||
| 			if !ok { | 			if !ok { | ||||||
|  | @ -112,10 +114,10 @@ func (p *processor) processFromClientAPI(ctx context.Context, clientMsg gtsmodel | ||||||
| 
 | 
 | ||||||
| 			return p.federateBlock(ctx, block) | 			return p.federateBlock(ctx, block) | ||||||
| 		} | 		} | ||||||
| 	case gtsmodel.ActivityStreamsUpdate: | 	case ap.ActivityUpdate: | ||||||
| 		// UPDATE | 		// UPDATE | ||||||
| 		switch clientMsg.APObjectType { | 		switch clientMsg.APObjectType { | ||||||
| 		case gtsmodel.ActivityStreamsProfile, gtsmodel.ActivityStreamsPerson: | 		case ap.ObjectProfile, ap.ActorPerson: | ||||||
| 			// UPDATE ACCOUNT/PROFILE | 			// UPDATE ACCOUNT/PROFILE | ||||||
| 			account, ok := clientMsg.GTSModel.(*gtsmodel.Account) | 			account, ok := clientMsg.GTSModel.(*gtsmodel.Account) | ||||||
| 			if !ok { | 			if !ok { | ||||||
|  | @ -124,10 +126,10 @@ func (p *processor) processFromClientAPI(ctx context.Context, clientMsg gtsmodel | ||||||
| 
 | 
 | ||||||
| 			return p.federateAccountUpdate(ctx, account, clientMsg.OriginAccount) | 			return p.federateAccountUpdate(ctx, account, clientMsg.OriginAccount) | ||||||
| 		} | 		} | ||||||
| 	case gtsmodel.ActivityStreamsAccept: | 	case ap.ActivityAccept: | ||||||
| 		// ACCEPT | 		// ACCEPT | ||||||
| 		switch clientMsg.APObjectType { | 		switch clientMsg.APObjectType { | ||||||
| 		case gtsmodel.ActivityStreamsFollow: | 		case ap.ActivityFollow: | ||||||
| 			// ACCEPT FOLLOW | 			// ACCEPT FOLLOW | ||||||
| 			follow, ok := clientMsg.GTSModel.(*gtsmodel.Follow) | 			follow, ok := clientMsg.GTSModel.(*gtsmodel.Follow) | ||||||
| 			if !ok { | 			if !ok { | ||||||
|  | @ -140,31 +142,31 @@ func (p *processor) processFromClientAPI(ctx context.Context, clientMsg gtsmodel | ||||||
| 
 | 
 | ||||||
| 			return p.federateAcceptFollowRequest(ctx, follow, clientMsg.OriginAccount, clientMsg.TargetAccount) | 			return p.federateAcceptFollowRequest(ctx, follow, clientMsg.OriginAccount, clientMsg.TargetAccount) | ||||||
| 		} | 		} | ||||||
| 	case gtsmodel.ActivityStreamsUndo: | 	case ap.ActivityUndo: | ||||||
| 		// UNDO | 		// UNDO | ||||||
| 		switch clientMsg.APObjectType { | 		switch clientMsg.APObjectType { | ||||||
| 		case gtsmodel.ActivityStreamsFollow: | 		case ap.ActivityFollow: | ||||||
| 			// UNDO FOLLOW | 			// UNDO FOLLOW | ||||||
| 			follow, ok := clientMsg.GTSModel.(*gtsmodel.Follow) | 			follow, ok := clientMsg.GTSModel.(*gtsmodel.Follow) | ||||||
| 			if !ok { | 			if !ok { | ||||||
| 				return errors.New("undo was not parseable as *gtsmodel.Follow") | 				return errors.New("undo was not parseable as *gtsmodel.Follow") | ||||||
| 			} | 			} | ||||||
| 			return p.federateUnfollow(ctx, follow, clientMsg.OriginAccount, clientMsg.TargetAccount) | 			return p.federateUnfollow(ctx, follow, clientMsg.OriginAccount, clientMsg.TargetAccount) | ||||||
| 		case gtsmodel.ActivityStreamsBlock: | 		case ap.ActivityBlock: | ||||||
| 			// UNDO BLOCK | 			// UNDO BLOCK | ||||||
| 			block, ok := clientMsg.GTSModel.(*gtsmodel.Block) | 			block, ok := clientMsg.GTSModel.(*gtsmodel.Block) | ||||||
| 			if !ok { | 			if !ok { | ||||||
| 				return errors.New("undo was not parseable as *gtsmodel.Block") | 				return errors.New("undo was not parseable as *gtsmodel.Block") | ||||||
| 			} | 			} | ||||||
| 			return p.federateUnblock(ctx, block) | 			return p.federateUnblock(ctx, block) | ||||||
| 		case gtsmodel.ActivityStreamsLike: | 		case ap.ActivityLike: | ||||||
| 			// UNDO LIKE/FAVE | 			// UNDO LIKE/FAVE | ||||||
| 			fave, ok := clientMsg.GTSModel.(*gtsmodel.StatusFave) | 			fave, ok := clientMsg.GTSModel.(*gtsmodel.StatusFave) | ||||||
| 			if !ok { | 			if !ok { | ||||||
| 				return errors.New("undo was not parseable as *gtsmodel.StatusFave") | 				return errors.New("undo was not parseable as *gtsmodel.StatusFave") | ||||||
| 			} | 			} | ||||||
| 			return p.federateUnfave(ctx, fave, clientMsg.OriginAccount, clientMsg.TargetAccount) | 			return p.federateUnfave(ctx, fave, clientMsg.OriginAccount, clientMsg.TargetAccount) | ||||||
| 		case gtsmodel.ActivityStreamsAnnounce: | 		case ap.ActivityAnnounce: | ||||||
| 			// UNDO ANNOUNCE/BOOST | 			// UNDO ANNOUNCE/BOOST | ||||||
| 			boost, ok := clientMsg.GTSModel.(*gtsmodel.Status) | 			boost, ok := clientMsg.GTSModel.(*gtsmodel.Status) | ||||||
| 			if !ok { | 			if !ok { | ||||||
|  | @ -177,10 +179,10 @@ func (p *processor) processFromClientAPI(ctx context.Context, clientMsg gtsmodel | ||||||
| 
 | 
 | ||||||
| 			return p.federateUnannounce(ctx, boost, clientMsg.OriginAccount, clientMsg.TargetAccount) | 			return p.federateUnannounce(ctx, boost, clientMsg.OriginAccount, clientMsg.TargetAccount) | ||||||
| 		} | 		} | ||||||
| 	case gtsmodel.ActivityStreamsDelete: | 	case ap.ActivityDelete: | ||||||
| 		// DELETE | 		// DELETE | ||||||
| 		switch clientMsg.APObjectType { | 		switch clientMsg.APObjectType { | ||||||
| 		case gtsmodel.ActivityStreamsNote: | 		case ap.ObjectNote: | ||||||
| 			// DELETE STATUS/NOTE | 			// DELETE STATUS/NOTE | ||||||
| 			statusToDelete, ok := clientMsg.GTSModel.(*gtsmodel.Status) | 			statusToDelete, ok := clientMsg.GTSModel.(*gtsmodel.Status) | ||||||
| 			if !ok { | 			if !ok { | ||||||
|  | @ -216,7 +218,7 @@ func (p *processor) processFromClientAPI(ctx context.Context, clientMsg gtsmodel | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			return p.federateStatusDelete(ctx, statusToDelete) | 			return p.federateStatusDelete(ctx, statusToDelete) | ||||||
| 		case gtsmodel.ActivityStreamsProfile, gtsmodel.ActivityStreamsPerson: | 		case ap.ObjectProfile, ap.ActorPerson: | ||||||
| 			// DELETE ACCOUNT/PROFILE | 			// DELETE ACCOUNT/PROFILE | ||||||
| 
 | 
 | ||||||
| 			// the origin of the delete could be either a domain block, or an action by another (or this) account | 			// the origin of the delete could be either a domain block, or an action by another (or this) account | ||||||
|  |  | ||||||
|  | @ -25,12 +25,14 @@ import ( | ||||||
| 	"net/url" | 	"net/url" | ||||||
| 
 | 
 | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/ap" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" | 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/id" | 	"github.com/superseriousbusiness/gotosocial/internal/id" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/messages" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func (p *processor) processFromFederator(ctx context.Context, federatorMsg gtsmodel.FromFederator) error { | func (p *processor) processFromFederator(ctx context.Context, federatorMsg messages.FromFederator) error { | ||||||
| 	l := p.log.WithFields(logrus.Fields{ | 	l := p.log.WithFields(logrus.Fields{ | ||||||
| 		"func":         "processFromFederator", | 		"func":         "processFromFederator", | ||||||
| 		"federatorMsg": fmt.Sprintf("%+v", federatorMsg), | 		"federatorMsg": fmt.Sprintf("%+v", federatorMsg), | ||||||
|  | @ -39,10 +41,10 @@ func (p *processor) processFromFederator(ctx context.Context, federatorMsg gtsmo | ||||||
| 	l.Trace("entering function PROCESS FROM FEDERATOR") | 	l.Trace("entering function PROCESS FROM FEDERATOR") | ||||||
| 
 | 
 | ||||||
| 	switch federatorMsg.APActivityType { | 	switch federatorMsg.APActivityType { | ||||||
| 	case gtsmodel.ActivityStreamsCreate: | 	case ap.ActivityCreate: | ||||||
| 		// CREATE | 		// CREATE | ||||||
| 		switch federatorMsg.APObjectType { | 		switch federatorMsg.APObjectType { | ||||||
| 		case gtsmodel.ActivityStreamsNote: | 		case ap.ObjectNote: | ||||||
| 			// CREATE A STATUS | 			// CREATE A STATUS | ||||||
| 			incomingStatus, ok := federatorMsg.GTSModel.(*gtsmodel.Status) | 			incomingStatus, ok := federatorMsg.GTSModel.(*gtsmodel.Status) | ||||||
| 			if !ok { | 			if !ok { | ||||||
|  | @ -61,10 +63,10 @@ func (p *processor) processFromFederator(ctx context.Context, federatorMsg gtsmo | ||||||
| 			if err := p.notifyStatus(ctx, status); err != nil { | 			if err := p.notifyStatus(ctx, status); err != nil { | ||||||
| 				return err | 				return err | ||||||
| 			} | 			} | ||||||
| 		case gtsmodel.ActivityStreamsProfile: | 		case ap.ObjectProfile: | ||||||
| 			// CREATE AN ACCOUNT | 			// CREATE AN ACCOUNT | ||||||
| 			// nothing to do here | 			// nothing to do here | ||||||
| 		case gtsmodel.ActivityStreamsLike: | 		case ap.ActivityLike: | ||||||
| 			// CREATE A FAVE | 			// CREATE A FAVE | ||||||
| 			incomingFave, ok := federatorMsg.GTSModel.(*gtsmodel.StatusFave) | 			incomingFave, ok := federatorMsg.GTSModel.(*gtsmodel.StatusFave) | ||||||
| 			if !ok { | 			if !ok { | ||||||
|  | @ -74,7 +76,7 @@ func (p *processor) processFromFederator(ctx context.Context, federatorMsg gtsmo | ||||||
| 			if err := p.notifyFave(ctx, incomingFave, federatorMsg.ReceivingAccount); err != nil { | 			if err := p.notifyFave(ctx, incomingFave, federatorMsg.ReceivingAccount); err != nil { | ||||||
| 				return err | 				return err | ||||||
| 			} | 			} | ||||||
| 		case gtsmodel.ActivityStreamsFollow: | 		case ap.ActivityFollow: | ||||||
| 			// CREATE A FOLLOW REQUEST | 			// CREATE A FOLLOW REQUEST | ||||||
| 			incomingFollowRequest, ok := federatorMsg.GTSModel.(*gtsmodel.FollowRequest) | 			incomingFollowRequest, ok := federatorMsg.GTSModel.(*gtsmodel.FollowRequest) | ||||||
| 			if !ok { | 			if !ok { | ||||||
|  | @ -84,7 +86,7 @@ func (p *processor) processFromFederator(ctx context.Context, federatorMsg gtsmo | ||||||
| 			if err := p.notifyFollowRequest(ctx, incomingFollowRequest, federatorMsg.ReceivingAccount); err != nil { | 			if err := p.notifyFollowRequest(ctx, incomingFollowRequest, federatorMsg.ReceivingAccount); err != nil { | ||||||
| 				return err | 				return err | ||||||
| 			} | 			} | ||||||
| 		case gtsmodel.ActivityStreamsAnnounce: | 		case ap.ActivityAnnounce: | ||||||
| 			// CREATE AN ANNOUNCE | 			// CREATE AN ANNOUNCE | ||||||
| 			incomingAnnounce, ok := federatorMsg.GTSModel.(*gtsmodel.Status) | 			incomingAnnounce, ok := federatorMsg.GTSModel.(*gtsmodel.Status) | ||||||
| 			if !ok { | 			if !ok { | ||||||
|  | @ -114,7 +116,7 @@ func (p *processor) processFromFederator(ctx context.Context, federatorMsg gtsmo | ||||||
| 			if err := p.notifyAnnounce(ctx, incomingAnnounce); err != nil { | 			if err := p.notifyAnnounce(ctx, incomingAnnounce); err != nil { | ||||||
| 				return err | 				return err | ||||||
| 			} | 			} | ||||||
| 		case gtsmodel.ActivityStreamsBlock: | 		case ap.ActivityBlock: | ||||||
| 			// CREATE A BLOCK | 			// CREATE A BLOCK | ||||||
| 			block, ok := federatorMsg.GTSModel.(*gtsmodel.Block) | 			block, ok := federatorMsg.GTSModel.(*gtsmodel.Block) | ||||||
| 			if !ok { | 			if !ok { | ||||||
|  | @ -131,10 +133,10 @@ func (p *processor) processFromFederator(ctx context.Context, federatorMsg gtsmo | ||||||
| 			// TODO: same with notifications | 			// TODO: same with notifications | ||||||
| 			// TODO: same with bookmarks | 			// TODO: same with bookmarks | ||||||
| 		} | 		} | ||||||
| 	case gtsmodel.ActivityStreamsUpdate: | 	case ap.ActivityUpdate: | ||||||
| 		// UPDATE | 		// UPDATE | ||||||
| 		switch federatorMsg.APObjectType { | 		switch federatorMsg.APObjectType { | ||||||
| 		case gtsmodel.ActivityStreamsProfile: | 		case ap.ObjectProfile: | ||||||
| 			// UPDATE AN ACCOUNT | 			// UPDATE AN ACCOUNT | ||||||
| 			incomingAccount, ok := federatorMsg.GTSModel.(*gtsmodel.Account) | 			incomingAccount, ok := federatorMsg.GTSModel.(*gtsmodel.Account) | ||||||
| 			if !ok { | 			if !ok { | ||||||
|  | @ -150,10 +152,10 @@ func (p *processor) processFromFederator(ctx context.Context, federatorMsg gtsmo | ||||||
| 				return fmt.Errorf("error dereferencing account from federator: %s", err) | 				return fmt.Errorf("error dereferencing account from federator: %s", err) | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	case gtsmodel.ActivityStreamsDelete: | 	case ap.ActivityDelete: | ||||||
| 		// DELETE | 		// DELETE | ||||||
| 		switch federatorMsg.APObjectType { | 		switch federatorMsg.APObjectType { | ||||||
| 		case gtsmodel.ActivityStreamsNote: | 		case ap.ObjectNote: | ||||||
| 			// DELETE A STATUS | 			// DELETE A STATUS | ||||||
| 			// TODO: handle side effects of status deletion here: | 			// TODO: handle side effects of status deletion here: | ||||||
| 			// 1. delete all media associated with status | 			// 1. delete all media associated with status | ||||||
|  | @ -185,14 +187,14 @@ func (p *processor) processFromFederator(ctx context.Context, federatorMsg gtsmo | ||||||
| 
 | 
 | ||||||
| 			// remove this status from any and all timelines | 			// remove this status from any and all timelines | ||||||
| 			return p.deleteStatusFromTimelines(ctx, statusToDelete) | 			return p.deleteStatusFromTimelines(ctx, statusToDelete) | ||||||
| 		case gtsmodel.ActivityStreamsProfile: | 		case ap.ObjectProfile: | ||||||
| 			// DELETE A PROFILE/ACCOUNT | 			// DELETE A PROFILE/ACCOUNT | ||||||
| 			// TODO: handle side effects of account deletion here: delete all objects, statuses, media etc associated with account | 			// TODO: handle side effects of account deletion here: delete all objects, statuses, media etc associated with account | ||||||
| 		} | 		} | ||||||
| 	case gtsmodel.ActivityStreamsAccept: | 	case ap.ActivityAccept: | ||||||
| 		// ACCEPT | 		// ACCEPT | ||||||
| 		switch federatorMsg.APObjectType { | 		switch federatorMsg.APObjectType { | ||||||
| 		case gtsmodel.ActivityStreamsFollow: | 		case ap.ActivityFollow: | ||||||
| 			// ACCEPT A FOLLOW | 			// ACCEPT A FOLLOW | ||||||
| 			follow, ok := federatorMsg.GTSModel.(*gtsmodel.Follow) | 			follow, ok := federatorMsg.GTSModel.(*gtsmodel.Follow) | ||||||
| 			if !ok { | 			if !ok { | ||||||
|  |  | ||||||
|  | @ -27,7 +27,7 @@ import ( | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/text" | 	"github.com/superseriousbusiness/gotosocial/internal/text" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/util" | 	"github.com/superseriousbusiness/gotosocial/internal/validate" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func (p *processor) InstanceGet(ctx context.Context, domain string) (*apimodel.Instance, gtserror.WithCode) { | func (p *processor) InstanceGet(ctx context.Context, domain string) (*apimodel.Instance, gtserror.WithCode) { | ||||||
|  | @ -59,7 +59,7 @@ func (p *processor) InstancePatch(ctx context.Context, form *apimodel.InstanceSe | ||||||
| 
 | 
 | ||||||
| 	// validate & update site title if it's set on the form | 	// validate & update site title if it's set on the form | ||||||
| 	if form.Title != nil { | 	if form.Title != nil { | ||||||
| 		if err := util.ValidateSiteTitle(*form.Title); err != nil { | 		if err := validate.SiteTitle(*form.Title); err != nil { | ||||||
| 			return nil, gtserror.NewErrorBadRequest(err, fmt.Sprintf("site title invalid: %s", err)) | 			return nil, gtserror.NewErrorBadRequest(err, fmt.Sprintf("site title invalid: %s", err)) | ||||||
| 		} | 		} | ||||||
| 		i.Title = text.RemoveHTML(*form.Title) // don't allow html in site title | 		i.Title = text.RemoveHTML(*form.Title) // don't allow html in site title | ||||||
|  | @ -101,7 +101,7 @@ func (p *processor) InstancePatch(ctx context.Context, form *apimodel.InstanceSe | ||||||
| 
 | 
 | ||||||
| 	// validate & update site contact email if it's set on the form | 	// validate & update site contact email if it's set on the form | ||||||
| 	if form.ContactEmail != nil { | 	if form.ContactEmail != nil { | ||||||
| 		if err := util.ValidateEmail(*form.ContactEmail); err != nil { | 		if err := validate.Email(*form.ContactEmail); err != nil { | ||||||
| 			return nil, gtserror.NewErrorBadRequest(err, err.Error()) | 			return nil, gtserror.NewErrorBadRequest(err, err.Error()) | ||||||
| 		} | 		} | ||||||
| 		i.ContactEmail = *form.ContactEmail | 		i.ContactEmail = *form.ContactEmail | ||||||
|  | @ -109,7 +109,7 @@ func (p *processor) InstancePatch(ctx context.Context, form *apimodel.InstanceSe | ||||||
| 
 | 
 | ||||||
| 	// validate & update site short description if it's set on the form | 	// validate & update site short description if it's set on the form | ||||||
| 	if form.ShortDescription != nil { | 	if form.ShortDescription != nil { | ||||||
| 		if err := util.ValidateSiteShortDescription(*form.ShortDescription); err != nil { | 		if err := validate.SiteShortDescription(*form.ShortDescription); err != nil { | ||||||
| 			return nil, gtserror.NewErrorBadRequest(err, err.Error()) | 			return nil, gtserror.NewErrorBadRequest(err, err.Error()) | ||||||
| 		} | 		} | ||||||
| 		i.ShortDescription = text.SanitizeHTML(*form.ShortDescription) // html is OK in site description, but we should sanitize it | 		i.ShortDescription = text.SanitizeHTML(*form.ShortDescription) // html is OK in site description, but we should sanitize it | ||||||
|  | @ -117,7 +117,7 @@ func (p *processor) InstancePatch(ctx context.Context, form *apimodel.InstanceSe | ||||||
| 
 | 
 | ||||||
| 	// validate & update site description if it's set on the form | 	// validate & update site description if it's set on the form | ||||||
| 	if form.Description != nil { | 	if form.Description != nil { | ||||||
| 		if err := util.ValidateSiteDescription(*form.Description); err != nil { | 		if err := validate.SiteDescription(*form.Description); err != nil { | ||||||
| 			return nil, gtserror.NewErrorBadRequest(err, err.Error()) | 			return nil, gtserror.NewErrorBadRequest(err, err.Error()) | ||||||
| 		} | 		} | ||||||
| 		i.Description = text.SanitizeHTML(*form.Description) // html is OK in site description, but we should sanitize it | 		i.Description = text.SanitizeHTML(*form.Description) // html is OK in site description, but we should sanitize it | ||||||
|  | @ -125,7 +125,7 @@ func (p *processor) InstancePatch(ctx context.Context, form *apimodel.InstanceSe | ||||||
| 
 | 
 | ||||||
| 	// validate & update site terms if it's set on the form | 	// validate & update site terms if it's set on the form | ||||||
| 	if form.Terms != nil { | 	if form.Terms != nil { | ||||||
| 		if err := util.ValidateSiteTerms(*form.Terms); err != nil { | 		if err := validate.SiteTerms(*form.Terms); err != nil { | ||||||
| 			return nil, gtserror.NewErrorBadRequest(err, err.Error()) | 			return nil, gtserror.NewErrorBadRequest(err, err.Error()) | ||||||
| 		} | 		} | ||||||
| 		i.Terms = text.SanitizeHTML(*form.Terms) // html is OK in site terms, but we should sanitize it | 		i.Terms = text.SanitizeHTML(*form.Terms) // html is OK in site terms, but we should sanitize it | ||||||
|  |  | ||||||
|  | @ -32,12 +32,14 @@ import ( | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/media" | 	"github.com/superseriousbusiness/gotosocial/internal/media" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/messages" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/oauth" | 	"github.com/superseriousbusiness/gotosocial/internal/oauth" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/processing/account" | 	"github.com/superseriousbusiness/gotosocial/internal/processing/account" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/processing/admin" | 	"github.com/superseriousbusiness/gotosocial/internal/processing/admin" | ||||||
| 	mediaProcessor "github.com/superseriousbusiness/gotosocial/internal/processing/media" | 	mediaProcessor "github.com/superseriousbusiness/gotosocial/internal/processing/media" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/processing/status" | 	"github.com/superseriousbusiness/gotosocial/internal/processing/status" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/processing/streaming" | 	"github.com/superseriousbusiness/gotosocial/internal/processing/streaming" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/stream" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/timeline" | 	"github.com/superseriousbusiness/gotosocial/internal/timeline" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/typeutils" | 	"github.com/superseriousbusiness/gotosocial/internal/typeutils" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/visibility" | 	"github.com/superseriousbusiness/gotosocial/internal/visibility" | ||||||
|  | @ -165,7 +167,7 @@ type Processor interface { | ||||||
| 	// AuthorizeStreamingRequest returns a gotosocial account in exchange for an access token, or an error if the given token is not valid. | 	// AuthorizeStreamingRequest returns a gotosocial account in exchange for an access token, or an error if the given token is not valid. | ||||||
| 	AuthorizeStreamingRequest(ctx context.Context, accessToken string) (*gtsmodel.Account, error) | 	AuthorizeStreamingRequest(ctx context.Context, accessToken string) (*gtsmodel.Account, error) | ||||||
| 	// OpenStreamForAccount opens a new stream for the given account, with the given stream type. | 	// OpenStreamForAccount opens a new stream for the given account, with the given stream type. | ||||||
| 	OpenStreamForAccount(ctx context.Context, account *gtsmodel.Account, streamType string) (*gtsmodel.Stream, gtserror.WithCode) | 	OpenStreamForAccount(ctx context.Context, account *gtsmodel.Account, streamType string) (*stream.Stream, gtserror.WithCode) | ||||||
| 
 | 
 | ||||||
| 	/* | 	/* | ||||||
| 		FEDERATION API-FACING PROCESSING FUNCTIONS | 		FEDERATION API-FACING PROCESSING FUNCTIONS | ||||||
|  | @ -219,8 +221,8 @@ type Processor interface { | ||||||
| 
 | 
 | ||||||
| // processor just implements the Processor interface | // processor just implements the Processor interface | ||||||
| type processor struct { | type processor struct { | ||||||
| 	fromClientAPI   chan gtsmodel.FromClientAPI | 	fromClientAPI   chan messages.FromClientAPI | ||||||
| 	fromFederator   chan gtsmodel.FromFederator | 	fromFederator   chan messages.FromFederator | ||||||
| 	federator       federation.Federator | 	federator       federation.Federator | ||||||
| 	stop            chan interface{} | 	stop            chan interface{} | ||||||
| 	log             *logrus.Logger | 	log             *logrus.Logger | ||||||
|  | @ -247,8 +249,8 @@ type processor struct { | ||||||
| // NewProcessor returns a new Processor that uses the given federator and logger | // NewProcessor returns a new Processor that uses the given federator and logger | ||||||
| func NewProcessor(config *config.Config, tc typeutils.TypeConverter, federator federation.Federator, oauthServer oauth.Server, mediaHandler media.Handler, storage blob.Storage, timelineManager timeline.Manager, db db.DB, log *logrus.Logger) Processor { | func NewProcessor(config *config.Config, tc typeutils.TypeConverter, federator federation.Federator, oauthServer oauth.Server, mediaHandler media.Handler, storage blob.Storage, timelineManager timeline.Manager, db db.DB, log *logrus.Logger) Processor { | ||||||
| 
 | 
 | ||||||
| 	fromClientAPI := make(chan gtsmodel.FromClientAPI, 1000) | 	fromClientAPI := make(chan messages.FromClientAPI, 1000) | ||||||
| 	fromFederator := make(chan gtsmodel.FromFederator, 1000) | 	fromFederator := make(chan messages.FromFederator, 1000) | ||||||
| 
 | 
 | ||||||
| 	statusProcessor := status.New(db, tc, config, fromClientAPI, log) | 	statusProcessor := status.New(db, tc, config, fromClientAPI, log) | ||||||
| 	streamingProcessor := streaming.New(db, tc, oauthServer, config, log) | 	streamingProcessor := streaming.New(db, tc, oauthServer, config, log) | ||||||
|  |  | ||||||
|  | @ -23,9 +23,11 @@ import ( | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 
 | 
 | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/ap" | ||||||
| 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" | 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/messages" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func (p *processor) Boost(ctx context.Context, requestingAccount *gtsmodel.Account, application *gtsmodel.Application, targetStatusID string) (*apimodel.Status, gtserror.WithCode) { | func (p *processor) Boost(ctx context.Context, requestingAccount *gtsmodel.Account, application *gtsmodel.Application, targetStatusID string) (*apimodel.Status, gtserror.WithCode) { | ||||||
|  | @ -44,10 +46,8 @@ func (p *processor) Boost(ctx context.Context, requestingAccount *gtsmodel.Accou | ||||||
| 	if !visible { | 	if !visible { | ||||||
| 		return nil, gtserror.NewErrorNotFound(errors.New("status is not visible")) | 		return nil, gtserror.NewErrorNotFound(errors.New("status is not visible")) | ||||||
| 	} | 	} | ||||||
| 	if targetStatus.VisibilityAdvanced != nil { | 	if !targetStatus.VisibilityAdvanced.Boostable { | ||||||
| 		if !targetStatus.VisibilityAdvanced.Boostable { | 		return nil, gtserror.NewErrorForbidden(errors.New("status is not boostable")) | ||||||
| 			return nil, gtserror.NewErrorForbidden(errors.New("status is not boostable")) |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// it's visible! it's boostable! so let's boost the FUCK out of it | 	// it's visible! it's boostable! so let's boost the FUCK out of it | ||||||
|  | @ -65,9 +65,9 @@ func (p *processor) Boost(ctx context.Context, requestingAccount *gtsmodel.Accou | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// send it back to the processor for async processing | 	// send it back to the processor for async processing | ||||||
| 	p.fromClientAPI <- gtsmodel.FromClientAPI{ | 	p.fromClientAPI <- messages.FromClientAPI{ | ||||||
| 		APObjectType:   gtsmodel.ActivityStreamsAnnounce, | 		APObjectType:   ap.ActivityAnnounce, | ||||||
| 		APActivityType: gtsmodel.ActivityStreamsCreate, | 		APActivityType: ap.ActivityCreate, | ||||||
| 		GTSModel:       boostWrapperStatus, | 		GTSModel:       boostWrapperStatus, | ||||||
| 		OriginAccount:  requestingAccount, | 		OriginAccount:  requestingAccount, | ||||||
| 		TargetAccount:  targetStatus.Account, | 		TargetAccount:  targetStatus.Account, | ||||||
|  |  | ||||||
|  | @ -23,10 +23,12 @@ import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/ap" | ||||||
| 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" | 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/id" | 	"github.com/superseriousbusiness/gotosocial/internal/id" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/messages" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/text" | 	"github.com/superseriousbusiness/gotosocial/internal/text" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/util" | 	"github.com/superseriousbusiness/gotosocial/internal/util" | ||||||
| ) | ) | ||||||
|  | @ -50,7 +52,7 @@ func (p *processor) Create(ctx context.Context, account *gtsmodel.Account, appli | ||||||
| 		AccountID:                account.ID, | 		AccountID:                account.ID, | ||||||
| 		AccountURI:               account.URI, | 		AccountURI:               account.URI, | ||||||
| 		ContentWarning:           text.RemoveHTML(form.SpoilerText), | 		ContentWarning:           text.RemoveHTML(form.SpoilerText), | ||||||
| 		ActivityStreamsType:      gtsmodel.ActivityStreamsNote, | 		ActivityStreamsType:      ap.ObjectNote, | ||||||
| 		Sensitive:                form.Sensitive, | 		Sensitive:                form.Sensitive, | ||||||
| 		Language:                 form.Language, | 		Language:                 form.Language, | ||||||
| 		CreatedWithApplicationID: application.ID, | 		CreatedWithApplicationID: application.ID, | ||||||
|  | @ -95,9 +97,9 @@ func (p *processor) Create(ctx context.Context, account *gtsmodel.Account, appli | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// send it back to the processor for async processing | 	// send it back to the processor for async processing | ||||||
| 	p.fromClientAPI <- gtsmodel.FromClientAPI{ | 	p.fromClientAPI <- messages.FromClientAPI{ | ||||||
| 		APObjectType:   gtsmodel.ActivityStreamsNote, | 		APObjectType:   ap.ObjectNote, | ||||||
| 		APActivityType: gtsmodel.ActivityStreamsCreate, | 		APActivityType: ap.ActivityCreate, | ||||||
| 		GTSModel:       newStatus, | 		GTSModel:       newStatus, | ||||||
| 		OriginAccount:  account, | 		OriginAccount:  account, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -23,9 +23,11 @@ import ( | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 
 | 
 | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/ap" | ||||||
| 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" | 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/messages" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func (p *processor) Delete(ctx context.Context, requestingAccount *gtsmodel.Account, targetStatusID string) (*apimodel.Status, gtserror.WithCode) { | func (p *processor) Delete(ctx context.Context, requestingAccount *gtsmodel.Account, targetStatusID string) (*apimodel.Status, gtserror.WithCode) { | ||||||
|  | @ -51,9 +53,9 @@ func (p *processor) Delete(ctx context.Context, requestingAccount *gtsmodel.Acco | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// send it back to the processor for async processing | 	// send it back to the processor for async processing | ||||||
| 	p.fromClientAPI <- gtsmodel.FromClientAPI{ | 	p.fromClientAPI <- messages.FromClientAPI{ | ||||||
| 		APObjectType:   gtsmodel.ActivityStreamsNote, | 		APObjectType:   ap.ObjectNote, | ||||||
| 		APActivityType: gtsmodel.ActivityStreamsDelete, | 		APActivityType: ap.ActivityDelete, | ||||||
| 		GTSModel:       targetStatus, | 		GTSModel:       targetStatus, | ||||||
| 		OriginAccount:  requestingAccount, | 		OriginAccount:  requestingAccount, | ||||||
| 		TargetAccount:  requestingAccount, | 		TargetAccount:  requestingAccount, | ||||||
|  |  | ||||||
|  | @ -23,11 +23,13 @@ import ( | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 
 | 
 | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/ap" | ||||||
| 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" | 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" | 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/id" | 	"github.com/superseriousbusiness/gotosocial/internal/id" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/messages" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/util" | 	"github.com/superseriousbusiness/gotosocial/internal/util" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | @ -47,10 +49,8 @@ func (p *processor) Fave(ctx context.Context, requestingAccount *gtsmodel.Accoun | ||||||
| 	if !visible { | 	if !visible { | ||||||
| 		return nil, gtserror.NewErrorNotFound(errors.New("status is not visible")) | 		return nil, gtserror.NewErrorNotFound(errors.New("status is not visible")) | ||||||
| 	} | 	} | ||||||
| 	if targetStatus.VisibilityAdvanced != nil { | 	if !targetStatus.VisibilityAdvanced.Likeable { | ||||||
| 		if !targetStatus.VisibilityAdvanced.Likeable { | 		return nil, gtserror.NewErrorForbidden(errors.New("status is not faveable")) | ||||||
| 			return nil, gtserror.NewErrorForbidden(errors.New("status is not faveable")) |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// first check if the status is already faved, if so we don't need to do anything | 	// first check if the status is already faved, if so we don't need to do anything | ||||||
|  | @ -84,9 +84,9 @@ func (p *processor) Fave(ctx context.Context, requestingAccount *gtsmodel.Accoun | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		// send it back to the processor for async processing | 		// send it back to the processor for async processing | ||||||
| 		p.fromClientAPI <- gtsmodel.FromClientAPI{ | 		p.fromClientAPI <- messages.FromClientAPI{ | ||||||
| 			APObjectType:   gtsmodel.ActivityStreamsLike, | 			APObjectType:   ap.ActivityLike, | ||||||
| 			APActivityType: gtsmodel.ActivityStreamsCreate, | 			APActivityType: ap.ActivityCreate, | ||||||
| 			GTSModel:       gtsFave, | 			GTSModel:       gtsFave, | ||||||
| 			OriginAccount:  requestingAccount, | 			OriginAccount:  requestingAccount, | ||||||
| 			TargetAccount:  targetStatus.Account, | 			TargetAccount:  targetStatus.Account, | ||||||
|  |  | ||||||
|  | @ -27,6 +27,7 @@ import ( | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" | 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/messages" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/text" | 	"github.com/superseriousbusiness/gotosocial/internal/text" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/typeutils" | 	"github.com/superseriousbusiness/gotosocial/internal/typeutils" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/visibility" | 	"github.com/superseriousbusiness/gotosocial/internal/visibility" | ||||||
|  | @ -75,12 +76,12 @@ type processor struct { | ||||||
| 	db            db.DB | 	db            db.DB | ||||||
| 	filter        visibility.Filter | 	filter        visibility.Filter | ||||||
| 	formatter     text.Formatter | 	formatter     text.Formatter | ||||||
| 	fromClientAPI chan gtsmodel.FromClientAPI | 	fromClientAPI chan messages.FromClientAPI | ||||||
| 	log           *logrus.Logger | 	log           *logrus.Logger | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // New returns a new status processor. | // New returns a new status processor. | ||||||
| func New(db db.DB, tc typeutils.TypeConverter, config *config.Config, fromClientAPI chan gtsmodel.FromClientAPI, log *logrus.Logger) Processor { | func New(db db.DB, tc typeutils.TypeConverter, config *config.Config, fromClientAPI chan messages.FromClientAPI, log *logrus.Logger) Processor { | ||||||
| 	return &processor{ | 	return &processor{ | ||||||
| 		tc:            tc, | 		tc:            tc, | ||||||
| 		config:        config, | 		config:        config, | ||||||
|  |  | ||||||
|  | @ -24,7 +24,7 @@ import ( | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/config" | 	"github.com/superseriousbusiness/gotosocial/internal/config" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" | 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/oauth" | 	"github.com/superseriousbusiness/gotosocial/internal/messages" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/processing/status" | 	"github.com/superseriousbusiness/gotosocial/internal/processing/status" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/typeutils" | 	"github.com/superseriousbusiness/gotosocial/internal/typeutils" | ||||||
| ) | ) | ||||||
|  | @ -36,11 +36,11 @@ type StatusStandardTestSuite struct { | ||||||
| 	db                db.DB | 	db                db.DB | ||||||
| 	log               *logrus.Logger | 	log               *logrus.Logger | ||||||
| 	typeConverter     typeutils.TypeConverter | 	typeConverter     typeutils.TypeConverter | ||||||
| 	fromClientAPIChan chan gtsmodel.FromClientAPI | 	fromClientAPIChan chan messages.FromClientAPI | ||||||
| 
 | 
 | ||||||
| 	// standard suite models | 	// standard suite models | ||||||
| 	testTokens       map[string]*oauth.Token | 	testTokens       map[string]*gtsmodel.Token | ||||||
| 	testClients      map[string]*oauth.Client | 	testClients      map[string]*gtsmodel.Client | ||||||
| 	testApplications map[string]*gtsmodel.Application | 	testApplications map[string]*gtsmodel.Application | ||||||
| 	testUsers        map[string]*gtsmodel.User | 	testUsers        map[string]*gtsmodel.User | ||||||
| 	testAccounts     map[string]*gtsmodel.Account | 	testAccounts     map[string]*gtsmodel.Account | ||||||
|  |  | ||||||
|  | @ -23,10 +23,12 @@ import ( | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 
 | 
 | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/ap" | ||||||
| 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" | 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" | 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/messages" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func (p *processor) Unboost(ctx context.Context, requestingAccount *gtsmodel.Account, application *gtsmodel.Application, targetStatusID string) (*apimodel.Status, gtserror.WithCode) { | func (p *processor) Unboost(ctx context.Context, requestingAccount *gtsmodel.Account, application *gtsmodel.Application, targetStatusID string) (*apimodel.Status, gtserror.WithCode) { | ||||||
|  | @ -89,9 +91,9 @@ func (p *processor) Unboost(ctx context.Context, requestingAccount *gtsmodel.Acc | ||||||
| 		gtsBoost.BoostOf.Account = targetStatus.Account | 		gtsBoost.BoostOf.Account = targetStatus.Account | ||||||
| 
 | 
 | ||||||
| 		// send it back to the processor for async processing | 		// send it back to the processor for async processing | ||||||
| 		p.fromClientAPI <- gtsmodel.FromClientAPI{ | 		p.fromClientAPI <- messages.FromClientAPI{ | ||||||
| 			APObjectType:   gtsmodel.ActivityStreamsAnnounce, | 			APObjectType:   ap.ActivityAnnounce, | ||||||
| 			APActivityType: gtsmodel.ActivityStreamsUndo, | 			APActivityType: ap.ActivityUndo, | ||||||
| 			GTSModel:       gtsBoost, | 			GTSModel:       gtsBoost, | ||||||
| 			OriginAccount:  requestingAccount, | 			OriginAccount:  requestingAccount, | ||||||
| 			TargetAccount:  targetStatus.Account, | 			TargetAccount:  targetStatus.Account, | ||||||
|  |  | ||||||
|  | @ -23,10 +23,12 @@ import ( | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 
 | 
 | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/ap" | ||||||
| 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" | 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" | 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/messages" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func (p *processor) Unfave(ctx context.Context, requestingAccount *gtsmodel.Account, targetStatusID string) (*apimodel.Status, gtserror.WithCode) { | func (p *processor) Unfave(ctx context.Context, requestingAccount *gtsmodel.Account, targetStatusID string) (*apimodel.Status, gtserror.WithCode) { | ||||||
|  | @ -71,9 +73,9 @@ func (p *processor) Unfave(ctx context.Context, requestingAccount *gtsmodel.Acco | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		// send it back to the processor for async processing | 		// send it back to the processor for async processing | ||||||
| 		p.fromClientAPI <- gtsmodel.FromClientAPI{ | 		p.fromClientAPI <- messages.FromClientAPI{ | ||||||
| 			APObjectType:   gtsmodel.ActivityStreamsLike, | 			APObjectType:   ap.ActivityLike, | ||||||
| 			APActivityType: gtsmodel.ActivityStreamsUndo, | 			APActivityType: ap.ActivityUndo, | ||||||
| 			GTSModel:       gtsFave, | 			GTSModel:       gtsFave, | ||||||
| 			OriginAccount:  requestingAccount, | 			OriginAccount:  requestingAccount, | ||||||
| 			TargetAccount:  targetStatus.Account, | 			TargetAccount:  targetStatus.Account, | ||||||
|  |  | ||||||
|  | @ -33,7 +33,7 @@ import ( | ||||||
| 
 | 
 | ||||||
| func (p *processor) ProcessVisibility(ctx context.Context, form *apimodel.AdvancedStatusCreateForm, accountDefaultVis gtsmodel.Visibility, status *gtsmodel.Status) error { | func (p *processor) ProcessVisibility(ctx context.Context, form *apimodel.AdvancedStatusCreateForm, accountDefaultVis gtsmodel.Visibility, status *gtsmodel.Status) error { | ||||||
| 	// by default all flags are set to true | 	// by default all flags are set to true | ||||||
| 	gtsAdvancedVis := >smodel.VisibilityAdvanced{ | 	gtsAdvancedVis := gtsmodel.VisibilityAdvanced{ | ||||||
| 		Federated: true, | 		Federated: true, | ||||||
| 		Boostable: true, | 		Boostable: true, | ||||||
| 		Replyable: true, | 		Replyable: true, | ||||||
|  | @ -123,11 +123,8 @@ func (p *processor) ProcessReplyToID(ctx context.Context, form *apimodel.Advance | ||||||
| 		} | 		} | ||||||
| 		return fmt.Errorf("status with id %s not replyable: %s", form.InReplyToID, err) | 		return fmt.Errorf("status with id %s not replyable: %s", form.InReplyToID, err) | ||||||
| 	} | 	} | ||||||
| 
 | 	if !repliedStatus.VisibilityAdvanced.Replyable { | ||||||
| 	if repliedStatus.VisibilityAdvanced != nil { | 		return fmt.Errorf("status with id %s is marked as not replyable", form.InReplyToID) | ||||||
| 		if !repliedStatus.VisibilityAdvanced.Replyable { |  | ||||||
| 			return fmt.Errorf("status with id %s is marked as not replyable", form.InReplyToID) |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// check replied account is known to us | 	// check replied account is known to us | ||||||
|  |  | ||||||
|  | @ -27,6 +27,7 @@ import ( | ||||||
| 	"github.com/stretchr/testify/suite" | 	"github.com/stretchr/testify/suite" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/api/model" | 	"github.com/superseriousbusiness/gotosocial/internal/api/model" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/messages" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/processing/status" | 	"github.com/superseriousbusiness/gotosocial/internal/processing/status" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/testrig" | 	"github.com/superseriousbusiness/gotosocial/testrig" | ||||||
| ) | ) | ||||||
|  | @ -68,7 +69,7 @@ func (suite *UtilTestSuite) SetupTest() { | ||||||
| 	suite.db = testrig.NewTestDB() | 	suite.db = testrig.NewTestDB() | ||||||
| 	suite.log = testrig.NewTestLog() | 	suite.log = testrig.NewTestLog() | ||||||
| 	suite.typeConverter = testrig.NewTestTypeConverter(suite.db) | 	suite.typeConverter = testrig.NewTestTypeConverter(suite.db) | ||||||
| 	suite.fromClientAPIChan = make(chan gtsmodel.FromClientAPI, 100) | 	suite.fromClientAPIChan = make(chan messages.FromClientAPI, 100) | ||||||
| 	suite.status = status.New(suite.db, suite.typeConverter, suite.config, suite.fromClientAPIChan, suite.log) | 	suite.status = status.New(suite.db, suite.typeConverter, suite.config, suite.fromClientAPIChan, suite.log) | ||||||
| 
 | 
 | ||||||
| 	testrig.StandardDBSetup(suite.db, nil) | 	testrig.StandardDBSetup(suite.db, nil) | ||||||
|  |  | ||||||
|  | @ -23,12 +23,13 @@ import ( | ||||||
| 
 | 
 | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/stream" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func (p *processor) AuthorizeStreamingRequest(ctx context.Context, accessToken string) (*gtsmodel.Account, error) { | func (p *processor) AuthorizeStreamingRequest(ctx context.Context, accessToken string) (*gtsmodel.Account, error) { | ||||||
| 	return p.streamingProcessor.AuthorizeStreamingRequest(ctx, accessToken) | 	return p.streamingProcessor.AuthorizeStreamingRequest(ctx, accessToken) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (p *processor) OpenStreamForAccount(ctx context.Context, account *gtsmodel.Account, streamType string) (*gtsmodel.Stream, gtserror.WithCode) { | func (p *processor) OpenStreamForAccount(ctx context.Context, account *gtsmodel.Account, streamType string) (*stream.Stream, gtserror.WithCode) { | ||||||
| 	return p.streamingProcessor.OpenStreamForAccount(ctx, account, streamType) | 	return p.streamingProcessor.OpenStreamForAccount(ctx, account, streamType) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -9,9 +9,10 @@ import ( | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/id" | 	"github.com/superseriousbusiness/gotosocial/internal/id" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/stream" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func (p *processor) OpenStreamForAccount(ctx context.Context, account *gtsmodel.Account, streamType string) (*gtsmodel.Stream, gtserror.WithCode) { | func (p *processor) OpenStreamForAccount(ctx context.Context, account *gtsmodel.Account, streamType string) (*stream.Stream, gtserror.WithCode) { | ||||||
| 	l := p.log.WithFields(logrus.Fields{ | 	l := p.log.WithFields(logrus.Fields{ | ||||||
| 		"func":       "OpenStreamForAccount", | 		"func":       "OpenStreamForAccount", | ||||||
| 		"account":    account.ID, | 		"account":    account.ID, | ||||||
|  | @ -25,10 +26,10 @@ func (p *processor) OpenStreamForAccount(ctx context.Context, account *gtsmodel. | ||||||
| 		return nil, gtserror.NewErrorInternalError(fmt.Errorf("error generating stream id: %s", err)) | 		return nil, gtserror.NewErrorInternalError(fmt.Errorf("error generating stream id: %s", err)) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	thisStream := >smodel.Stream{ | 	thisStream := &stream.Stream{ | ||||||
| 		ID:        streamID, | 		ID:        streamID, | ||||||
| 		Type:      streamType, | 		Type:      streamType, | ||||||
| 		Messages:  make(chan *gtsmodel.Message, 100), | 		Messages:  make(chan *stream.Message, 100), | ||||||
| 		Hangup:    make(chan interface{}, 1), | 		Hangup:    make(chan interface{}, 1), | ||||||
| 		Connected: true, | 		Connected: true, | ||||||
| 	} | 	} | ||||||
|  | @ -37,8 +38,8 @@ func (p *processor) OpenStreamForAccount(ctx context.Context, account *gtsmodel. | ||||||
| 	v, ok := p.streamMap.Load(account.ID) | 	v, ok := p.streamMap.Load(account.ID) | ||||||
| 	if !ok || v == nil { | 	if !ok || v == nil { | ||||||
| 		// there is no entry in the streamMap for this account yet, so make one and store it | 		// there is no entry in the streamMap for this account yet, so make one and store it | ||||||
| 		streamsForAccount := >smodel.StreamsForAccount{ | 		streamsForAccount := &stream.StreamsForAccount{ | ||||||
| 			Streams: []*gtsmodel.Stream{ | 			Streams: []*stream.Stream{ | ||||||
| 				thisStream, | 				thisStream, | ||||||
| 			}, | 			}, | ||||||
| 		} | 		} | ||||||
|  | @ -46,7 +47,7 @@ func (p *processor) OpenStreamForAccount(ctx context.Context, account *gtsmodel. | ||||||
| 	} else { | 	} else { | ||||||
| 		// there is an entry in the streamMap for this account | 		// there is an entry in the streamMap for this account | ||||||
| 		// parse the interface as a streamsForAccount | 		// parse the interface as a streamsForAccount | ||||||
| 		streamsForAccount, ok := v.(*gtsmodel.StreamsForAccount) | 		streamsForAccount, ok := v.(*stream.StreamsForAccount) | ||||||
| 		if !ok { | 		if !ok { | ||||||
| 			return nil, gtserror.NewErrorInternalError(errors.New("stream map error")) | 			return nil, gtserror.NewErrorInternalError(errors.New("stream map error")) | ||||||
| 		} | 		} | ||||||
|  | @ -63,7 +64,7 @@ func (p *processor) OpenStreamForAccount(ctx context.Context, account *gtsmodel. | ||||||
| // waitToCloseStream waits until the hangup channel is closed for the given stream. | // waitToCloseStream waits until the hangup channel is closed for the given stream. | ||||||
| // It then iterates through the map of streams stored by the processor, removes the stream from it, | // It then iterates through the map of streams stored by the processor, removes the stream from it, | ||||||
| // and then closes the messages channel of the stream to indicate that the channel should no longer be read from. | // and then closes the messages channel of the stream to indicate that the channel should no longer be read from. | ||||||
| func (p *processor) waitToCloseStream(account *gtsmodel.Account, thisStream *gtsmodel.Stream) { | func (p *processor) waitToCloseStream(account *gtsmodel.Account, thisStream *stream.Stream) { | ||||||
| 	<-thisStream.Hangup // wait for a hangup message | 	<-thisStream.Hangup // wait for a hangup message | ||||||
| 
 | 
 | ||||||
| 	// lock the stream to prevent more messages being put in it while we work | 	// lock the stream to prevent more messages being put in it while we work | ||||||
|  | @ -78,7 +79,7 @@ func (p *processor) waitToCloseStream(account *gtsmodel.Account, thisStream *gts | ||||||
| 	if !ok || v == nil { | 	if !ok || v == nil { | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	streamsForAccount, ok := v.(*gtsmodel.StreamsForAccount) | 	streamsForAccount, ok := v.(*stream.StreamsForAccount) | ||||||
| 	if !ok { | 	if !ok { | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  | @ -88,7 +89,7 @@ func (p *processor) waitToCloseStream(account *gtsmodel.Account, thisStream *gts | ||||||
| 	defer streamsForAccount.Unlock() | 	defer streamsForAccount.Unlock() | ||||||
| 
 | 
 | ||||||
| 	// put everything into modified streams *except* the stream we're removing | 	// put everything into modified streams *except* the stream we're removing | ||||||
| 	modifiedStreams := []*gtsmodel.Stream{} | 	modifiedStreams := []*stream.Stream{} | ||||||
| 	for _, s := range streamsForAccount.Streams { | 	for _, s := range streamsForAccount.Streams { | ||||||
| 		if s.ID != thisStream.ID { | 		if s.ID != thisStream.ID { | ||||||
| 			modifiedStreams = append(modifiedStreams, s) | 			modifiedStreams = append(modifiedStreams, s) | ||||||
|  |  | ||||||
|  | @ -4,7 +4,7 @@ import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"strings" | 	"strings" | ||||||
| 
 | 
 | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/stream" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func (p *processor) StreamDelete(statusID string) error { | func (p *processor) StreamDelete(statusID string) error { | ||||||
|  | @ -20,7 +20,7 @@ func (p *processor) StreamDelete(statusID string) error { | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		// the value of the map should be a buncha streams | 		// the value of the map should be a buncha streams | ||||||
| 		streamsForAccount, ok := v.(*gtsmodel.StreamsForAccount) | 		streamsForAccount, ok := v.(*stream.StreamsForAccount) | ||||||
| 		if !ok { | 		if !ok { | ||||||
| 			errs = append(errs, fmt.Sprintf("stream map error for account stream %s", accountID)) | 			errs = append(errs, fmt.Sprintf("stream map error for account stream %s", accountID)) | ||||||
| 		} | 		} | ||||||
|  | @ -28,13 +28,13 @@ func (p *processor) StreamDelete(statusID string) error { | ||||||
| 		// lock the streams while we work on them | 		// lock the streams while we work on them | ||||||
| 		streamsForAccount.Lock() | 		streamsForAccount.Lock() | ||||||
| 		defer streamsForAccount.Unlock() | 		defer streamsForAccount.Unlock() | ||||||
| 		for _, stream := range streamsForAccount.Streams { | 		for _, s := range streamsForAccount.Streams { | ||||||
| 			// lock each individual stream as we work on it | 			// lock each individual stream as we work on it | ||||||
| 			stream.Lock() | 			s.Lock() | ||||||
| 			defer stream.Unlock() | 			defer s.Unlock() | ||||||
| 			if stream.Connected { | 			if s.Connected { | ||||||
| 				stream.Messages <- >smodel.Message{ | 				s.Messages <- &stream.Message{ | ||||||
| 					Stream:  []string{stream.Type}, | 					Stream:  []string{s.Type}, | ||||||
| 					Event:   "delete", | 					Event:   "delete", | ||||||
| 					Payload: statusID, | 					Payload: statusID, | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
|  | @ -11,6 +11,7 @@ import ( | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/oauth" | 	"github.com/superseriousbusiness/gotosocial/internal/oauth" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/stream" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/typeutils" | 	"github.com/superseriousbusiness/gotosocial/internal/typeutils" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/visibility" | 	"github.com/superseriousbusiness/gotosocial/internal/visibility" | ||||||
| ) | ) | ||||||
|  | @ -20,7 +21,7 @@ type Processor interface { | ||||||
| 	// AuthorizeStreamingRequest returns an oauth2 token info in response to an access token query from the streaming API | 	// AuthorizeStreamingRequest returns an oauth2 token info in response to an access token query from the streaming API | ||||||
| 	AuthorizeStreamingRequest(ctx context.Context, accessToken string) (*gtsmodel.Account, error) | 	AuthorizeStreamingRequest(ctx context.Context, accessToken string) (*gtsmodel.Account, error) | ||||||
| 	// OpenStreamForAccount returns a new Stream for the given account, which will contain a channel for passing messages back to the caller. | 	// OpenStreamForAccount returns a new Stream for the given account, which will contain a channel for passing messages back to the caller. | ||||||
| 	OpenStreamForAccount(ctx context.Context, account *gtsmodel.Account, streamType string) (*gtsmodel.Stream, gtserror.WithCode) | 	OpenStreamForAccount(ctx context.Context, account *gtsmodel.Account, streamType string) (*stream.Stream, gtserror.WithCode) | ||||||
| 	// StreamStatusToAccount streams the given status to any open, appropriate streams belonging to the given account. | 	// StreamStatusToAccount streams the given status to any open, appropriate streams belonging to the given account. | ||||||
| 	StreamStatusToAccount(s *apimodel.Status, account *gtsmodel.Account) error | 	StreamStatusToAccount(s *apimodel.Status, account *gtsmodel.Account) error | ||||||
| 	// StreamNotificationToAccount streams the given notification to any open, appropriate streams belonging to the given account. | 	// StreamNotificationToAccount streams the given notification to any open, appropriate streams belonging to the given account. | ||||||
|  |  | ||||||
|  | @ -8,6 +8,7 @@ import ( | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
| 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" | 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/stream" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func (p *processor) StreamNotificationToAccount(n *apimodel.Notification, account *gtsmodel.Account) error { | func (p *processor) StreamNotificationToAccount(n *apimodel.Notification, account *gtsmodel.Account) error { | ||||||
|  | @ -21,7 +22,7 @@ func (p *processor) StreamNotificationToAccount(n *apimodel.Notification, accoun | ||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	streamsForAccount, ok := v.(*gtsmodel.StreamsForAccount) | 	streamsForAccount, ok := v.(*stream.StreamsForAccount) | ||||||
| 	if !ok { | 	if !ok { | ||||||
| 		return errors.New("stream map error") | 		return errors.New("stream map error") | ||||||
| 	} | 	} | ||||||
|  | @ -33,13 +34,13 @@ func (p *processor) StreamNotificationToAccount(n *apimodel.Notification, accoun | ||||||
| 
 | 
 | ||||||
| 	streamsForAccount.Lock() | 	streamsForAccount.Lock() | ||||||
| 	defer streamsForAccount.Unlock() | 	defer streamsForAccount.Unlock() | ||||||
| 	for _, stream := range streamsForAccount.Streams { | 	for _, s := range streamsForAccount.Streams { | ||||||
| 		stream.Lock() | 		s.Lock() | ||||||
| 		defer stream.Unlock() | 		defer s.Unlock() | ||||||
| 		if stream.Connected { | 		if s.Connected { | ||||||
| 			l.Debugf("streaming notification to stream id %s", stream.ID) | 			l.Debugf("streaming notification to stream id %s", s.ID) | ||||||
| 			stream.Messages <- >smodel.Message{ | 			s.Messages <- &stream.Message{ | ||||||
| 				Stream:  []string{stream.Type}, | 				Stream:  []string{s.Type}, | ||||||
| 				Event:   "notification", | 				Event:   "notification", | ||||||
| 				Payload: string(notificationBytes), | 				Payload: string(notificationBytes), | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
|  | @ -8,6 +8,7 @@ import ( | ||||||
| 	"github.com/sirupsen/logrus" | 	"github.com/sirupsen/logrus" | ||||||
| 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" | 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/stream" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func (p *processor) StreamStatusToAccount(s *apimodel.Status, account *gtsmodel.Account) error { | func (p *processor) StreamStatusToAccount(s *apimodel.Status, account *gtsmodel.Account) error { | ||||||
|  | @ -21,7 +22,7 @@ func (p *processor) StreamStatusToAccount(s *apimodel.Status, account *gtsmodel. | ||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	streamsForAccount, ok := v.(*gtsmodel.StreamsForAccount) | 	streamsForAccount, ok := v.(*stream.StreamsForAccount) | ||||||
| 	if !ok { | 	if !ok { | ||||||
| 		return errors.New("stream map error") | 		return errors.New("stream map error") | ||||||
| 	} | 	} | ||||||
|  | @ -33,13 +34,13 @@ func (p *processor) StreamStatusToAccount(s *apimodel.Status, account *gtsmodel. | ||||||
| 
 | 
 | ||||||
| 	streamsForAccount.Lock() | 	streamsForAccount.Lock() | ||||||
| 	defer streamsForAccount.Unlock() | 	defer streamsForAccount.Unlock() | ||||||
| 	for _, stream := range streamsForAccount.Streams { | 	for _, s := range streamsForAccount.Streams { | ||||||
| 		stream.Lock() | 		s.Lock() | ||||||
| 		defer stream.Unlock() | 		defer s.Unlock() | ||||||
| 		if stream.Connected { | 		if s.Connected { | ||||||
| 			l.Debugf("streaming status to stream id %s", stream.ID) | 			l.Debugf("streaming status to stream id %s", s.ID) | ||||||
| 			stream.Messages <- >smodel.Message{ | 			s.Messages <- &stream.Message{ | ||||||
| 				Stream:  []string{stream.Type}, | 				Stream:  []string{s.Type}, | ||||||
| 				Event:   "update", | 				Event:   "update", | ||||||
| 				Payload: string(statusBytes), | 				Payload: string(statusBytes), | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
							
								
								
									
										136
									
								
								internal/regexes/regexes.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										136
									
								
								internal/regexes/regexes.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,136 @@ | ||||||
|  | /* | ||||||
|  |    GoToSocial | ||||||
|  |    Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org | ||||||
|  | 
 | ||||||
|  |    This program is free software: you can redistribute it and/or modify | ||||||
|  |    it under the terms of the GNU Affero General Public License as published by | ||||||
|  |    the Free Software Foundation, either version 3 of the License, or | ||||||
|  |    (at your option) any later version. | ||||||
|  | 
 | ||||||
|  |    This program is distributed in the hope that it will be useful, | ||||||
|  |    but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  |    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  |    GNU Affero General Public License for more details. | ||||||
|  | 
 | ||||||
|  |    You should have received a copy of the GNU Affero General Public License | ||||||
|  |    along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||||
|  | */ | ||||||
|  | 
 | ||||||
|  | package regexes | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"regexp" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	users     = "users" | ||||||
|  | 	actors    = "actors" | ||||||
|  | 	statuses  = "statuses" | ||||||
|  | 	inbox     = "inbox" | ||||||
|  | 	outbox    = "outbox" | ||||||
|  | 	followers = "followers" | ||||||
|  | 	following = "following" | ||||||
|  | 	liked     = "liked" | ||||||
|  | 	// collections = "collections" | ||||||
|  | 	// featured    = "featured" | ||||||
|  | 	publicKey = "main-key" | ||||||
|  | 	follow    = "follow" | ||||||
|  | 	// update      = "updates" | ||||||
|  | 	blocks = "blocks" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | const ( | ||||||
|  | 	maximumUsernameLength       = 64 | ||||||
|  | 	maximumEmojiShortcodeLength = 30 | ||||||
|  | 	maximumHashtagLength        = 30 | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | var ( | ||||||
|  | 	mentionName = `^@(\w+)(?:@([a-zA-Z0-9_\-\.:]+)?)$` | ||||||
|  | 	// MentionName captures the username and domain part from a mention string | ||||||
|  | 	// such as @whatever_user@example.org, returning whatever_user and example.org (without the @ symbols) | ||||||
|  | 	MentionName = regexp.MustCompile(mentionName) | ||||||
|  | 
 | ||||||
|  | 	// mention regex can be played around with here: https://regex101.com/r/qwM9D3/1 | ||||||
|  | 	mentionFinder = `(?:\B)(@\w+(?:@[a-zA-Z0-9_\-\.]+)?)(?:\B)?` | ||||||
|  | 	// MentionFinder extracts mentions from a piece of text. | ||||||
|  | 	MentionFinder = regexp.MustCompile(mentionFinder) | ||||||
|  | 
 | ||||||
|  | 	// hashtag regex can be played with here: https://regex101.com/r/bPxeca/1 | ||||||
|  | 	hashtagFinder = fmt.Sprintf(`(?:^|\n|\s)(#[a-zA-Z0-9]{1,%d})(?:\b)`, maximumHashtagLength) | ||||||
|  | 	// HashtagFinder finds possible hashtags in a string. | ||||||
|  | 	// It returns just the string part of the hashtag, not the # symbol. | ||||||
|  | 	HashtagFinder = regexp.MustCompile(hashtagFinder) | ||||||
|  | 
 | ||||||
|  | 	emojiShortcode = fmt.Sprintf(`\w{2,%d}`, maximumEmojiShortcodeLength) | ||||||
|  | 	// EmojiShortcode validates an emoji name. | ||||||
|  | 	EmojiShortcode = regexp.MustCompile(fmt.Sprintf("^%s$", emojiShortcode)) | ||||||
|  | 
 | ||||||
|  | 	// emoji regex can be played with here: https://regex101.com/r/478XGM/1 | ||||||
|  | 	emojiFinderString = fmt.Sprintf(`(?:\B)?:(%s):(?:\B)?`, emojiShortcode) | ||||||
|  | 	// EmojiFinder extracts emoji strings from a piece of text. | ||||||
|  | 	EmojiFinder = regexp.MustCompile(emojiFinderString) | ||||||
|  | 
 | ||||||
|  | 	// usernameString defines an acceptable username on this instance | ||||||
|  | 	usernameString = fmt.Sprintf(`[a-z0-9_]{2,%d}`, maximumUsernameLength) | ||||||
|  | 	// Username can be used to validate usernames of new signups | ||||||
|  | 	Username = regexp.MustCompile(fmt.Sprintf(`^%s$`, usernameString)) | ||||||
|  | 
 | ||||||
|  | 	userPathString = fmt.Sprintf(`^?/%s/(%s)$`, users, usernameString) | ||||||
|  | 	// UserPath parses a path that validates and captures the username part from eg /users/example_username | ||||||
|  | 	UserPath = regexp.MustCompile(userPathString) | ||||||
|  | 
 | ||||||
|  | 	publicKeyPath = fmt.Sprintf(`^?/%s/(%s)/%s`, users, usernameString, publicKey) | ||||||
|  | 	// PublicKeyPath parses a path that validates and captures the username part from eg /users/example_username/main-key | ||||||
|  | 	PublicKeyPath = regexp.MustCompile(publicKeyPath) | ||||||
|  | 
 | ||||||
|  | 	inboxPath = fmt.Sprintf(`^/?%s/(%s)/%s$`, users, usernameString, inbox) | ||||||
|  | 	// InboxPath parses a path that validates and captures the username part from eg /users/example_username/inbox | ||||||
|  | 	InboxPath = regexp.MustCompile(inboxPath) | ||||||
|  | 
 | ||||||
|  | 	outboxPath = fmt.Sprintf(`^/?%s/(%s)/%s$`, users, usernameString, outbox) | ||||||
|  | 	// OutboxPath parses a path that validates and captures the username part from eg /users/example_username/outbox | ||||||
|  | 	OutboxPath = regexp.MustCompile(outboxPath) | ||||||
|  | 
 | ||||||
|  | 	actorPath = fmt.Sprintf(`^?/%s/(%s)$`, actors, usernameString) | ||||||
|  | 	// ActorPath parses a path that validates and captures the username part from eg /actors/example_username | ||||||
|  | 	ActorPath = regexp.MustCompile(actorPath) | ||||||
|  | 
 | ||||||
|  | 	followersPath = fmt.Sprintf(`^/?%s/(%s)/%s$`, users, usernameString, followers) | ||||||
|  | 	// FollowersPath parses a path that validates and captures the username part from eg /users/example_username/followers | ||||||
|  | 	FollowersPath = regexp.MustCompile(followersPath) | ||||||
|  | 
 | ||||||
|  | 	followingPath = fmt.Sprintf(`^/?%s/(%s)/%s$`, users, usernameString, following) | ||||||
|  | 	// FollowingPath parses a path that validates and captures the username part from eg /users/example_username/following | ||||||
|  | 	FollowingPath = regexp.MustCompile(followingPath) | ||||||
|  | 
 | ||||||
|  | 	followPath = fmt.Sprintf(`^/?%s/(%s)/%s/(%s)$`, users, usernameString, follow, ulid) | ||||||
|  | 	// FollowPath parses a path that validates and captures the username part and the ulid part | ||||||
|  | 	// from eg /users/example_username/follow/01F7XT5JZW1WMVSW1KADS8PVDH | ||||||
|  | 	FollowPath = regexp.MustCompile(followPath) | ||||||
|  | 
 | ||||||
|  | 	ulid = `[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{26}` | ||||||
|  | 	// ULID parses and validate a ULID. | ||||||
|  | 	ULID = regexp.MustCompile(fmt.Sprintf(`^%s$`, ulid)) | ||||||
|  | 
 | ||||||
|  | 	likedPath = fmt.Sprintf(`^/?%s/(%s)/%s$`, users, usernameString, liked) | ||||||
|  | 	// LikedPath parses a path that validates and captures the username part from eg /users/example_username/liked | ||||||
|  | 	LikedPath = regexp.MustCompile(likedPath) | ||||||
|  | 
 | ||||||
|  | 	likePath = fmt.Sprintf(`^/?%s/(%s)/%s/(%s)$`, users, usernameString, liked, ulid) | ||||||
|  | 	// LikePath parses a path that validates and captures the username part and the ulid part | ||||||
|  | 	// from eg /users/example_username/like/01F7XT5JZW1WMVSW1KADS8PVDH | ||||||
|  | 	LikePath = regexp.MustCompile(likePath) | ||||||
|  | 
 | ||||||
|  | 	statusesPath = fmt.Sprintf(`^/?%s/(%s)/%s/(%s)$`, users, usernameString, statuses, ulid) | ||||||
|  | 	// StatusesPath parses a path that validates and captures the username part and the ulid part | ||||||
|  | 	// from eg /users/example_username/statuses/01F7XT5JZW1WMVSW1KADS8PVDH | ||||||
|  | 	// The regex can be played with here: https://regex101.com/r/G9zuxQ/1 | ||||||
|  | 	StatusesPath = regexp.MustCompile(statusesPath) | ||||||
|  | 
 | ||||||
|  | 	blockPath = fmt.Sprintf(`^/?%s/(%s)/%s/(%s)$`, users, usernameString, blocks, ulid) | ||||||
|  | 	// BlockPath parses a path that validates and captures the username part and the ulid part | ||||||
|  | 	// from eg /users/example_username/blocks/01F7XT5JZW1WMVSW1KADS8PVDH | ||||||
|  | 	BlockPath = regexp.MustCompile(blockPath) | ||||||
|  | ) | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| package gtsmodel | package stream | ||||||
| 
 | 
 | ||||||
| import "sync" | import "sync" | ||||||
| 
 | 
 | ||||||
|  | @ -25,7 +25,7 @@ import ( | ||||||
| 	"strings" | 	"strings" | ||||||
| 
 | 
 | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/util" | 	"github.com/superseriousbusiness/gotosocial/internal/regexes" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // preformat contains some common logic for making a string ready for formatting, which should be used for all user-input text. | // preformat contains some common logic for making a string ready for formatting, which should be used for all user-input text. | ||||||
|  | @ -61,7 +61,7 @@ func postformat(in string) string { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (f *formatter) ReplaceTags(ctx context.Context, in string, tags []*gtsmodel.Tag) string { | func (f *formatter) ReplaceTags(ctx context.Context, in string, tags []*gtsmodel.Tag) string { | ||||||
| 	return util.HashtagFinderRegex.ReplaceAllStringFunc(in, func(match string) string { | 	return regexes.HashtagFinder.ReplaceAllStringFunc(in, func(match string) string { | ||||||
| 		// we have a match | 		// we have a match | ||||||
| 		matchTrimmed := strings.TrimSpace(match) | 		matchTrimmed := strings.TrimSpace(match) | ||||||
| 		tagAsEntered := strings.Split(matchTrimmed, "#")[1] | 		tagAsEntered := strings.Split(matchTrimmed, "#")[1] | ||||||
|  |  | ||||||
|  | @ -24,7 +24,6 @@ import ( | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/config" | 	"github.com/superseriousbusiness/gotosocial/internal/config" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/db" | 	"github.com/superseriousbusiness/gotosocial/internal/db" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/oauth" |  | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/text" | 	"github.com/superseriousbusiness/gotosocial/internal/text" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | @ -37,8 +36,8 @@ type TextStandardTestSuite struct { | ||||||
| 	log    *logrus.Logger | 	log    *logrus.Logger | ||||||
| 
 | 
 | ||||||
| 	// standard suite models | 	// standard suite models | ||||||
| 	testTokens       map[string]*oauth.Token | 	testTokens       map[string]*gtsmodel.Token | ||||||
| 	testClients      map[string]*oauth.Client | 	testClients      map[string]*gtsmodel.Client | ||||||
| 	testApplications map[string]*gtsmodel.Application | 	testApplications map[string]*gtsmodel.Application | ||||||
| 	testUsers        map[string]*gtsmodel.User | 	testUsers        map[string]*gtsmodel.User | ||||||
| 	testAccounts     map[string]*gtsmodel.Account | 	testAccounts     map[string]*gtsmodel.Account | ||||||
|  |  | ||||||
|  | @ -32,6 +32,7 @@ import ( | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/id" | 	"github.com/superseriousbusiness/gotosocial/internal/id" | ||||||
| 	"github.com/superseriousbusiness/gotosocial/internal/util" | 	"github.com/superseriousbusiness/gotosocial/internal/util" | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/validate" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func (t *transport) DereferenceInstance(ctx context.Context, iri *url.URL) (*gtsmodel.Instance, error) { | func (t *transport) DereferenceInstance(ctx context.Context, iri *url.URL) (*gtsmodel.Instance, error) { | ||||||
|  | @ -199,7 +200,7 @@ func dereferenceByNodeInfo(c context.Context, t *transport, iri *url.URL) (*gtsm | ||||||
| 		if v, ok := i.(map[string]string); ok { | 		if v, ok := i.(map[string]string); ok { | ||||||
| 			// see if there's an email in the map | 			// see if there's an email in the map | ||||||
| 			if email, present := v["email"]; present { | 			if email, present := v["email"]; present { | ||||||
| 				if err := util.ValidateEmail(email); err == nil { | 				if err := validate.Email(email); err == nil { | ||||||
| 					// valid email address | 					// valid email address | ||||||
| 					contactEmail = email | 					contactEmail = email | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
|  | @ -94,15 +94,15 @@ func (c *converter) ASRepresentationToAccount(ctx context.Context, accountable a | ||||||
| 
 | 
 | ||||||
| 	// check for bot and actor type | 	// check for bot and actor type | ||||||
| 	switch accountable.GetTypeName() { | 	switch accountable.GetTypeName() { | ||||||
| 	case gtsmodel.ActivityStreamsPerson, gtsmodel.ActivityStreamsGroup, gtsmodel.ActivityStreamsOrganization: | 	case ap.ActorPerson, ap.ActorGroup, ap.ActorOrganization: | ||||||
| 		// people, groups, and organizations aren't bots | 		// people, groups, and organizations aren't bots | ||||||
| 		acct.Bot = false | 		acct.Bot = false | ||||||
| 		// apps and services are | 		// apps and services are | ||||||
| 	case gtsmodel.ActivityStreamsApplication, gtsmodel.ActivityStreamsService: | 	case ap.ActorApplication, ap.ActorService: | ||||||
| 		acct.Bot = true | 		acct.Bot = true | ||||||
| 	default: | 	default: | ||||||
| 		// we don't know what this is! | 		// we don't know what this is! | ||||||
| 		return nil, fmt.Errorf("type name %s not recognised or not convertible to gtsmodel.ActivityStreamsActor", accountable.GetTypeName()) | 		return nil, fmt.Errorf("type name %s not recognised or not convertible to ap.ActivityStreamsActor", accountable.GetTypeName()) | ||||||
| 	} | 	} | ||||||
| 	acct.ActorType = accountable.GetTypeName() | 	acct.ActorType = accountable.GetTypeName() | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -227,7 +227,6 @@ func (c *converter) AppToMastoSensitive(ctx context.Context, a *gtsmodel.Applica | ||||||
| 		RedirectURI:  a.RedirectURI, | 		RedirectURI:  a.RedirectURI, | ||||||
| 		ClientID:     a.ClientID, | 		ClientID:     a.ClientID, | ||||||
| 		ClientSecret: a.ClientSecret, | 		ClientSecret: a.ClientSecret, | ||||||
| 		VapidKey:     a.VapidKey, |  | ||||||
| 	}, nil | 	}, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,113 +0,0 @@ | ||||||
| /* |  | ||||||
|    GoToSocial |  | ||||||
|    Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org |  | ||||||
| 
 |  | ||||||
|    This program is free software: you can redistribute it and/or modify |  | ||||||
|    it under the terms of the GNU Affero General Public License as published by |  | ||||||
|    the Free Software Foundation, either version 3 of the License, or |  | ||||||
|    (at your option) any later version. |  | ||||||
| 
 |  | ||||||
|    This program is distributed in the hope that it will be useful, |  | ||||||
|    but WITHOUT ANY WARRANTY; without even the implied warranty of |  | ||||||
|    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the |  | ||||||
|    GNU Affero General Public License for more details. |  | ||||||
| 
 |  | ||||||
|    You should have received a copy of the GNU Affero General Public License |  | ||||||
|    along with this program.  If not, see <http://www.gnu.org/licenses/>. |  | ||||||
| */ |  | ||||||
| 
 |  | ||||||
| package util |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"fmt" |  | ||||||
| 	"regexp" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| const ( |  | ||||||
| 	maximumUsernameLength       = 64 |  | ||||||
| 	maximumEmojiShortcodeLength = 30 |  | ||||||
| 	maximumHashtagLength        = 30 |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| var ( |  | ||||||
| 	mentionNameRegexString = `^@(\w+)(?:@([a-zA-Z0-9_\-\.:]+)?)$` |  | ||||||
| 	// mention name regex captures the username and domain part from a mention string |  | ||||||
| 	// such as @whatever_user@example.org, returning whatever_user and example.org (without the @ symbols) |  | ||||||
| 	mentionNameRegex = regexp.MustCompile(mentionNameRegexString) |  | ||||||
| 
 |  | ||||||
| 	// mention regex can be played around with here: https://regex101.com/r/qwM9D3/1 |  | ||||||
| 	mentionFinderRegexString = `(?:\B)(@\w+(?:@[a-zA-Z0-9_\-\.]+)?)(?:\B)?` |  | ||||||
| 	mentionFinderRegex       = regexp.MustCompile(mentionFinderRegexString) |  | ||||||
| 
 |  | ||||||
| 	// hashtag regex can be played with here: https://regex101.com/r/bPxeca/1 |  | ||||||
| 	hashtagFinderRegexString = fmt.Sprintf(`(?:^|\n|\s)(#[a-zA-Z0-9]{1,%d})(?:\b)`, maximumHashtagLength) |  | ||||||
| 	// HashtagFinderRegex finds possible hashtags in a string. |  | ||||||
| 	// It returns just the string part of the hashtag, not the # symbol. |  | ||||||
| 	HashtagFinderRegex = regexp.MustCompile(hashtagFinderRegexString) |  | ||||||
| 
 |  | ||||||
| 	emojiShortcodeRegexString     = fmt.Sprintf(`\w{2,%d}`, maximumEmojiShortcodeLength) |  | ||||||
| 	emojiShortcodeValidationRegex = regexp.MustCompile(fmt.Sprintf("^%s$", emojiShortcodeRegexString)) |  | ||||||
| 
 |  | ||||||
| 	// emoji regex can be played with here: https://regex101.com/r/478XGM/1 |  | ||||||
| 	emojiFinderRegexString = fmt.Sprintf(`(?:\B)?:(%s):(?:\B)?`, emojiShortcodeRegexString) |  | ||||||
| 	emojiFinderRegex       = regexp.MustCompile(emojiFinderRegexString) |  | ||||||
| 
 |  | ||||||
| 	// usernameRegexString defines an acceptable username on this instance |  | ||||||
| 	usernameRegexString = fmt.Sprintf(`[a-z0-9_]{2,%d}`, maximumUsernameLength) |  | ||||||
| 	// usernameValidationRegex can be used to validate usernames of new signups |  | ||||||
| 	usernameValidationRegex = regexp.MustCompile(fmt.Sprintf(`^%s$`, usernameRegexString)) |  | ||||||
| 
 |  | ||||||
| 	userPathRegexString = fmt.Sprintf(`^?/%s/(%s)$`, UsersPath, usernameRegexString) |  | ||||||
| 	// userPathRegex parses a path that validates and captures the username part from eg /users/example_username |  | ||||||
| 	userPathRegex = regexp.MustCompile(userPathRegexString) |  | ||||||
| 
 |  | ||||||
| 	userPublicKeyPathRegexString = fmt.Sprintf(`^?/%s/(%s)/%s`, UsersPath, usernameRegexString, PublicKeyPath) |  | ||||||
| 	userPublicKeyPathRegex       = regexp.MustCompile(userPublicKeyPathRegexString) |  | ||||||
| 
 |  | ||||||
| 	inboxPathRegexString = fmt.Sprintf(`^/?%s/(%s)/%s$`, UsersPath, usernameRegexString, InboxPath) |  | ||||||
| 	// inboxPathRegex parses a path that validates and captures the username part from eg /users/example_username/inbox |  | ||||||
| 	inboxPathRegex = regexp.MustCompile(inboxPathRegexString) |  | ||||||
| 
 |  | ||||||
| 	outboxPathRegexString = fmt.Sprintf(`^/?%s/(%s)/%s$`, UsersPath, usernameRegexString, OutboxPath) |  | ||||||
| 	// outboxPathRegex parses a path that validates and captures the username part from eg /users/example_username/outbox |  | ||||||
| 	outboxPathRegex = regexp.MustCompile(outboxPathRegexString) |  | ||||||
| 
 |  | ||||||
| 	actorPathRegexString = fmt.Sprintf(`^?/%s/(%s)$`, ActorsPath, usernameRegexString) |  | ||||||
| 	// actorPathRegex parses a path that validates and captures the username part from eg /actors/example_username |  | ||||||
| 	actorPathRegex = regexp.MustCompile(actorPathRegexString) |  | ||||||
| 
 |  | ||||||
| 	followersPathRegexString = fmt.Sprintf(`^/?%s/(%s)/%s$`, UsersPath, usernameRegexString, FollowersPath) |  | ||||||
| 	// followersPathRegex parses a path that validates and captures the username part from eg /users/example_username/followers |  | ||||||
| 	followersPathRegex = regexp.MustCompile(followersPathRegexString) |  | ||||||
| 
 |  | ||||||
| 	followingPathRegexString = fmt.Sprintf(`^/?%s/(%s)/%s$`, UsersPath, usernameRegexString, FollowingPath) |  | ||||||
| 	// followingPathRegex parses a path that validates and captures the username part from eg /users/example_username/following |  | ||||||
| 	followingPathRegex = regexp.MustCompile(followingPathRegexString) |  | ||||||
| 
 |  | ||||||
| 	followPathRegexString = fmt.Sprintf(`^/?%s/(%s)/%s/(%s)$`, UsersPath, usernameRegexString, FollowPath, ulidRegexString) |  | ||||||
| 	// followPathRegex parses a path that validates and captures the username part and the ulid part |  | ||||||
| 	// from eg /users/example_username/follow/01F7XT5JZW1WMVSW1KADS8PVDH |  | ||||||
| 	followPathRegex = regexp.MustCompile(followPathRegexString) |  | ||||||
| 
 |  | ||||||
| 	ulidRegexString = `[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{26}` |  | ||||||
| 
 |  | ||||||
| 	likedPathRegexString = fmt.Sprintf(`^/?%s/(%s)/%s$`, UsersPath, usernameRegexString, LikedPath) |  | ||||||
| 	// likedPathRegex parses a path that validates and captures the username part from eg /users/example_username/liked |  | ||||||
| 	likedPathRegex = regexp.MustCompile(likedPathRegexString) |  | ||||||
| 
 |  | ||||||
| 	likePathRegexString = fmt.Sprintf(`^/?%s/(%s)/%s/(%s)$`, UsersPath, usernameRegexString, LikedPath, ulidRegexString) |  | ||||||
| 	// likePathRegex parses a path that validates and captures the username part and the ulid part |  | ||||||
| 	// from eg /users/example_username/like/01F7XT5JZW1WMVSW1KADS8PVDH |  | ||||||
| 	likePathRegex = regexp.MustCompile(likePathRegexString) |  | ||||||
| 
 |  | ||||||
| 	statusesPathRegexString = fmt.Sprintf(`^/?%s/(%s)/%s/(%s)$`, UsersPath, usernameRegexString, StatusesPath, ulidRegexString) |  | ||||||
| 	// statusesPathRegex parses a path that validates and captures the username part and the ulid part |  | ||||||
| 	// from eg /users/example_username/statuses/01F7XT5JZW1WMVSW1KADS8PVDH |  | ||||||
| 	// The regex can be played with here: https://regex101.com/r/G9zuxQ/1 |  | ||||||
| 	statusesPathRegex = regexp.MustCompile(statusesPathRegexString) |  | ||||||
| 
 |  | ||||||
| 	blockPathRegexString = fmt.Sprintf(`^/?%s/(%s)/%s/(%s)$`, UsersPath, usernameRegexString, BlocksPath, ulidRegexString) |  | ||||||
| 	// blockPathRegex parses a path that validates and captures the username part and the ulid part |  | ||||||
| 	// from eg /users/example_username/blocks/01F7XT5JZW1WMVSW1KADS8PVDH |  | ||||||
| 	blockPathRegex = regexp.MustCompile(blockPathRegexString) |  | ||||||
| ) |  | ||||||
|  | @ -21,6 +21,8 @@ package util | ||||||
| import ( | import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"strings" | 	"strings" | ||||||
|  | 
 | ||||||
|  | 	"github.com/superseriousbusiness/gotosocial/internal/regexes" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // DeriveMentionsFromStatus takes a plaintext (ie., not html-formatted) status, | // DeriveMentionsFromStatus takes a plaintext (ie., not html-formatted) status, | ||||||
|  | @ -31,7 +33,7 @@ import ( | ||||||
| // or the form "@username" for local users. | // or the form "@username" for local users. | ||||||
| func DeriveMentionsFromStatus(status string) []string { | func DeriveMentionsFromStatus(status string) []string { | ||||||
| 	mentionedAccounts := []string{} | 	mentionedAccounts := []string{} | ||||||
| 	for _, m := range mentionFinderRegex.FindAllStringSubmatch(status, -1) { | 	for _, m := range regexes.MentionFinder.FindAllStringSubmatch(status, -1) { | ||||||
| 		mentionedAccounts = append(mentionedAccounts, m[1]) | 		mentionedAccounts = append(mentionedAccounts, m[1]) | ||||||
| 	} | 	} | ||||||
| 	return UniqueStrings(mentionedAccounts) | 	return UniqueStrings(mentionedAccounts) | ||||||
|  | @ -43,7 +45,7 @@ func DeriveMentionsFromStatus(status string) []string { | ||||||
| // tags will be lowered, for consistency. | // tags will be lowered, for consistency. | ||||||
| func DeriveHashtagsFromStatus(status string) []string { | func DeriveHashtagsFromStatus(status string) []string { | ||||||
| 	tags := []string{} | 	tags := []string{} | ||||||
| 	for _, m := range HashtagFinderRegex.FindAllStringSubmatch(status, -1) { | 	for _, m := range regexes.HashtagFinder.FindAllStringSubmatch(status, -1) { | ||||||
| 		tags = append(tags, strings.TrimPrefix(m[1], "#")) | 		tags = append(tags, strings.TrimPrefix(m[1], "#")) | ||||||
| 	} | 	} | ||||||
| 	return UniqueStrings(tags) | 	return UniqueStrings(tags) | ||||||
|  | @ -54,7 +56,7 @@ func DeriveHashtagsFromStatus(status string) []string { | ||||||
| // used in that status, without the surround ::. | // used in that status, without the surround ::. | ||||||
| func DeriveEmojisFromStatus(status string) []string { | func DeriveEmojisFromStatus(status string) []string { | ||||||
| 	emojis := []string{} | 	emojis := []string{} | ||||||
| 	for _, m := range emojiFinderRegex.FindAllStringSubmatch(status, -1) { | 	for _, m := range regexes.EmojiFinder.FindAllStringSubmatch(status, -1) { | ||||||
| 		emojis = append(emojis, m[1]) | 		emojis = append(emojis, m[1]) | ||||||
| 	} | 	} | ||||||
| 	return UniqueStrings(emojis) | 	return UniqueStrings(emojis) | ||||||
|  | @ -65,7 +67,7 @@ func DeriveEmojisFromStatus(status string) []string { | ||||||
| // | // | ||||||
| // If nothing is matched, it will return an error. | // If nothing is matched, it will return an error. | ||||||
| func ExtractMentionParts(mention string) (username, domain string, err error) { | func ExtractMentionParts(mention string) (username, domain string, err error) { | ||||||
| 	matches := mentionNameRegex.FindStringSubmatch(mention) | 	matches := regexes.MentionName.FindStringSubmatch(mention) | ||||||
| 	if matches == nil || len(matches) != 3 { | 	if matches == nil || len(matches) != 3 { | ||||||
| 		err = fmt.Errorf("could't match mention %s", mention) | 		err = fmt.Errorf("could't match mention %s", mention) | ||||||
| 		return | 		return | ||||||
|  | @ -77,5 +79,5 @@ func ExtractMentionParts(mention string) (username, domain string, err error) { | ||||||
| 
 | 
 | ||||||
| // IsMention returns true if the passed string looks like @whatever@example.org | // IsMention returns true if the passed string looks like @whatever@example.org | ||||||
| func IsMention(mention string) bool { | func IsMention(mention string) bool { | ||||||
| 	return mentionNameRegex.MatchString(strings.ToLower(mention)) | 	return regexes.MentionName.MatchString(strings.ToLower(mention)) | ||||||
| } | } | ||||||
|  |  | ||||||
Some files were not shown because too many files have changed in this diff Show more
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue