Merge pull request #186 from superseriousbusiness/struct_validation

Struct validation
This commit is contained in:
kim 2021-09-03 10:27:40 +01:00 committed by GitHub
commit 25edd57eaf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
133 changed files with 5030 additions and 1476 deletions

View file

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

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

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

View file

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

View file

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

View file

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

View file

@ -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 = &gtsmodel.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{}, &gtsmodel.Client{},
&oauth.Token{}, &gtsmodel.Token{},
&gtsmodel.User{}, &gtsmodel.User{},
&gtsmodel.Account{}, &gtsmodel.Account{},
&gtsmodel.Application{}, &gtsmodel.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{}, &gtsmodel.Client{},
&oauth.Token{}, &gtsmodel.Token{},
&gtsmodel.User{}, &gtsmodel.User{},
&gtsmodel.Account{}, &gtsmodel.Account{},
&gtsmodel.Application{}, &gtsmodel.Application{},

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -73,8 +73,8 @@ var models []interface{} = []interface{}{
&gtsmodel.Instance{}, &gtsmodel.Instance{},
&gtsmodel.Notification{}, &gtsmodel.Notification{},
&gtsmodel.RouterSession{}, &gtsmodel.RouterSession{},
&oauth.Token{}, &gtsmodel.Token{},
&oauth.Client{}, &gtsmodel.Client{},
} }
// Start creates and starts a gotosocial server // Start creates and starts a gotosocial server

View file

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

View file

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

View file

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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, &gtsmodel.Status{}); err != nil { if err := f.db.DeleteByID(ctx, s.ID, &gtsmodel.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, &gtsmodel.Account{}); err != nil { if err := f.db.DeleteByID(ctx, a.ID, &gtsmodel.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,
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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: &gtsmodel.Follow{ GTSModel: &gtsmodel.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: &gtsmodel.Follow{ GTSModel: &gtsmodel.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,

View file

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

View file

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

View file

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

View file

@ -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: &gtsmodel.Follow{ GTSModel: &gtsmodel.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: &gtsmodel.Follow{ GTSModel: &gtsmodel.Follow{
AccountID: requestingAccount.ID, AccountID: requestingAccount.ID,
TargetAccountID: targetAccountID, TargetAccountID: targetAccountID,

View file

@ -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, &gtsmodel.Account{}); err != nil { if err := p.db.UpdateOneByID(ctx, account.ID, "language", *form.Source.Language, &gtsmodel.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, &gtsmodel.Account{}); err != nil { if err := p.db.UpdateOneByID(ctx, account.ID, "privacy", *form.Source.Privacy, &gtsmodel.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,
} }

View file

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

View file

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

View file

@ -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 := &gtsmodel.Client{
ID: clientID, ID: clientID,
Secret: clientSecret, Secret: clientSecret,
Domain: form.RedirectURIs, Domain: form.RedirectURIs,

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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 <- &gtsmodel.Message{ s.Messages <- &stream.Message{
Stream: []string{stream.Type}, Stream: []string{s.Type},
Event: "delete", Event: "delete",
Payload: statusID, Payload: statusID,
} }

View file

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

View file

@ -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 <- &gtsmodel.Message{ s.Messages <- &stream.Message{
Stream: []string{stream.Type}, Stream: []string{s.Type},
Event: "notification", Event: "notification",
Payload: string(notificationBytes), Payload: string(notificationBytes),
} }

View file

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

View file

@ -1,4 +1,4 @@
package gtsmodel package stream
import "sync" import "sync"

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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