From ef7e0cfe4f89ac1cc9636e72552e3eb301d17139 Mon Sep 17 00:00:00 2001 From: tsmethurst Date: Tue, 24 Aug 2021 23:07:14 +0200 Subject: [PATCH] tests passing --- internal/api/client/auth/callback.go | 17 ++++++----- internal/db/admin.go | 4 +-- internal/db/pg/account.go | 8 ++--- internal/db/pg/admin.go | 40 ++++++------------------ internal/db/pg/pg.go | 2 -- internal/db/pg/status.go | 44 +++++++++++++++++++++++---- internal/db/pg/status_test.go | 5 ++- internal/db/pg/timeline.go | 25 +++++++-------- internal/gtsmodel/account.go | 6 ++-- internal/gtsmodel/block.go | 8 ++--- internal/gtsmodel/domainblock.go | 6 ++-- internal/gtsmodel/emaildomainblock.go | 4 +-- internal/gtsmodel/emoji.go | 9 +++--- internal/gtsmodel/follow.go | 8 ++--- internal/gtsmodel/followrequest.go | 8 ++--- internal/gtsmodel/instance.go | 14 ++++----- internal/gtsmodel/mediaattachment.go | 8 ++--- internal/gtsmodel/mention.go | 4 +-- internal/gtsmodel/messages.go | 32 ++++++++++--------- internal/gtsmodel/notification.go | 4 +-- internal/gtsmodel/routersession.go | 4 +-- internal/gtsmodel/status.go | 18 +++++------ internal/gtsmodel/statusbookmark.go | 2 +- internal/gtsmodel/statusfave.go | 2 +- internal/gtsmodel/statusmute.go | 2 +- internal/gtsmodel/tag.go | 8 ++--- internal/gtsmodel/user.go | 10 +++--- internal/processing/account/create.go | 12 ++++++-- 28 files changed, 168 insertions(+), 146 deletions(-) diff --git a/internal/api/client/auth/callback.go b/internal/api/client/auth/callback.go index 1e19907c1..cbb429352 100644 --- a/internal/api/client/auth/callback.go +++ b/internal/api/client/auth/callback.go @@ -138,9 +138,13 @@ func (m *Module) parseUserFromClaims(ctx context.Context, claims *oidc.Claims, i // however, because we trust the OIDC provider, we should now create a user + account with the provided claims // check if the email address is available for use; if it's not there's nothing we can so - if err := m.db.IsEmailAvailable(ctx, claims.Email); err != nil { + emailAvailable, err := m.db.IsEmailAvailable(ctx, claims.Email) + if err != nil { return nil, fmt.Errorf("email %s not available: %s", claims.Email, err) } + if !emailAvailable { + return nil, fmt.Errorf("email %s in use", claims.Email) + } // now we need a username var username string @@ -181,12 +185,11 @@ func (m *Module) parseUserFromClaims(ctx context.Context, claims *oidc.Claims, i // note that for the first iteration, iString is still "" when the check is made, so our first choice // is still the raw username with no integer stuck on the end for i := 1; !found; i = i + 1 { - if err := m.db.IsUsernameAvailable(ctx, username+iString); err != nil { - if strings.Contains(err.Error(), "db error") { - // if there's an actual db error we should return - return nil, fmt.Errorf("error checking username availability: %s", err) - } - } else { + usernameAvailable, err := m.db.IsUsernameAvailable(ctx, username+iString) + if err != nil { + return nil, err + } + if usernameAvailable { // no error so we've found a username that works found = true username = username + iString diff --git a/internal/db/admin.go b/internal/db/admin.go index f9b4f821e..24d628e84 100644 --- a/internal/db/admin.go +++ b/internal/db/admin.go @@ -29,14 +29,14 @@ import ( type Admin interface { // IsUsernameAvailable checks whether a given username is available on our domain. // Returns an error if the username is already taken, or something went wrong in the db. - IsUsernameAvailable(ctx context.Context, username string) Error + IsUsernameAvailable(ctx context.Context, username string) (bool, Error) // IsEmailAvailable checks whether a given email address for a new account is available to be used on our domain. // Return an error if: // A) the email is already associated with an account // B) we block signups from this email domain // C) something went wrong in the db - IsEmailAvailable(ctx context.Context, email string) Error + IsEmailAvailable(ctx context.Context, email string) (bool, Error) // NewSignup creates a new user in the database with the given parameters. // By the time this function is called, it should be assumed that all the parameters have passed validation! diff --git a/internal/db/pg/account.go b/internal/db/pg/account.go index d448d4dc4..73b71cf11 100644 --- a/internal/db/pg/account.go +++ b/internal/db/pg/account.go @@ -206,6 +206,10 @@ func (a *accountDB) GetAccountStatuses(ctx context.Context, accountID string, li q = q.Where("pinned = ?", true) } + if maxID != "" { + q = q.Where("id < ?", maxID) + } + if mediaOnly { q = q.WhereGroup(" AND ", func(q *bun.SelectQuery) *bun.SelectQuery { return q. @@ -214,10 +218,6 @@ func (a *accountDB) GetAccountStatuses(ctx context.Context, accountID string, li }) } - if maxID != "" { - q = q.Where("id < ?", maxID) - } - if err := q.Scan(ctx); err != nil { return nil, err } diff --git a/internal/db/pg/admin.go b/internal/db/pg/admin.go index 8eab95e5d..e9fd01b11 100644 --- a/internal/db/pg/admin.go +++ b/internal/db/pg/admin.go @@ -46,37 +46,21 @@ type adminDB struct { cancel context.CancelFunc } -func (a *adminDB) IsUsernameAvailable(ctx context.Context, username string) db.Error { - // if no error we fail because it means we found something - // if err is pg.ErrNoRows we're good, we found nothing so continue - // if error but it's not sql.ErrNoRows then we fail +func (a *adminDB) IsUsernameAvailable(ctx context.Context, username string) (bool, db.Error) { q := a.conn. NewSelect(). Model(>smodel.Account{}). Where("username = ?", username). Where("domain = ?", nil) - err := q.Scan(ctx) - - if err == nil { - // we got something, not good - return fmt.Errorf("username %s already in use", username) - } - - if err == sql.ErrNoRows { - // no entries, we're happy - return nil - } - - // another type of error occurred - return processErrorResponse(err) + return notExists(ctx, q) } -func (a *adminDB) IsEmailAvailable(ctx context.Context, email string) db.Error { +func (a *adminDB) IsEmailAvailable(ctx context.Context, email string) (bool, db.Error) { // parse the domain from the email m, err := mail.ParseAddress(email) if err != nil { - return fmt.Errorf("error parsing email address %s: %s", email, err) + return false, fmt.Errorf("error parsing email address %s: %s", email, err) } domain := strings.Split(m.Address, "@")[1] // domain will always be the second part after @ @@ -87,25 +71,19 @@ func (a *adminDB) IsEmailAvailable(ctx context.Context, email string) db.Error { Where("domain = ?", domain). Scan(ctx); err == nil { // fail because we found something - return fmt.Errorf("email domain %s is blocked", domain) + return false, fmt.Errorf("email domain %s is blocked", domain) } else if err != sql.ErrNoRows { - return processErrorResponse(err) + return false, processErrorResponse(err) } // check if this email is associated with a user already - if err := a.conn. + q := a.conn. NewSelect(). Model(>smodel.User{}). Where("email = ?", email). - WhereOr("unconfirmed_email = ?", email). - Scan(ctx); err == nil { - // fail because we found something - return fmt.Errorf("email %s already in use", email) - } else if err != sql.ErrNoRows { - return processErrorResponse(err) - } + WhereOr("unconfirmed_email = ?", email) - return nil + return notExists(ctx, q) } func (a *adminDB) NewSignup(ctx context.Context, username string, reason string, requireApproval bool, email string, password string, signUpIP net.IP, locale string, appID string, emailVerified bool, admin bool) (*gtsmodel.User, db.Error) { diff --git a/internal/db/pg/pg.go b/internal/db/pg/pg.go index a33a9466a..5d1a1d01a 100644 --- a/internal/db/pg/pg.go +++ b/internal/db/pg/pg.go @@ -73,7 +73,6 @@ func NewPostgresService(ctx context.Context, c *config.Config, log *logrus.Logge if err != nil { return nil, fmt.Errorf("could not create postgres options: %s", err) } - log.Debugf("using opts %+v", opts) sqldb := stdlib.OpenDB(*opts) conn := bun.NewDB(sqldb, pgdialect.New()) @@ -87,7 +86,6 @@ func NewPostgresService(ctx context.Context, c *config.Config, log *logrus.Logge // https://bun.uptrace.dev/orm/many-to-many-relation/ conn.RegisterModel(t) } - log.Info("models registered") ps := &postgresService{ Account: &accountDB{ diff --git a/internal/db/pg/status.go b/internal/db/pg/status.go index 92f611e68..e4609a116 100644 --- a/internal/db/pg/status.go +++ b/internal/db/pg/status.go @@ -76,7 +76,31 @@ func (s *statusDB) newStatusQ(status interface{}) *bun.SelectQuery { Relation("Attachments"). Relation("Tags"). Relation("Mentions"). - Relation("Emojis") + Relation("Emojis"). + Relation("Account"). + Relation("InReplyToAccount"). + Relation("BoostOfAccount"). + Relation("CreatedWithApplication") +} + +func (s *statusDB) getAttachedStatuses(ctx context.Context, status *gtsmodel.Status) *gtsmodel.Status { + if status.InReplyToID != "" && status.InReplyTo == nil { + if inReplyTo, cached := s.statusCached(status.InReplyToID); cached { + status.InReplyTo = inReplyTo + } else if inReplyTo, err := s.GetStatusByID(ctx, status.InReplyToID); err == nil { + status.InReplyTo = inReplyTo + } + } + + if status.BoostOfID != "" && status.BoostOf == nil { + if boostOf, cached := s.statusCached(status.BoostOfID); cached { + status.BoostOf = boostOf + } else if boostOf, err := s.GetStatusByID(ctx, status.BoostOfID); err == nil { + status.BoostOf = boostOf + } + } + + return status } func (s *statusDB) newFaveQ(faves interface{}) *bun.SelectQuery { @@ -108,7 +132,7 @@ func (s *statusDB) GetStatusByID(ctx context.Context, id string) (*gtsmodel.Stat s.cacheStatus(id, status) } - return status, err + return s.getAttachedStatuses(ctx, status), err } func (s *statusDB) GetStatusByURI(ctx context.Context, uri string) (*gtsmodel.Status, db.Error) { @@ -123,11 +147,15 @@ func (s *statusDB) GetStatusByURI(ctx context.Context, uri string) (*gtsmodel.St err := processErrorResponse(q.Scan(ctx)) - if err == nil && status != nil { + if err != nil { + return nil, err + } + + if status != nil { s.cacheStatus(uri, status) } - return status, err + return s.getAttachedStatuses(ctx, status), err } func (s *statusDB) GetStatusByURL(ctx context.Context, uri string) (*gtsmodel.Status, db.Error) { @@ -142,11 +170,15 @@ func (s *statusDB) GetStatusByURL(ctx context.Context, uri string) (*gtsmodel.St err := processErrorResponse(q.Scan(ctx)) - if err == nil && status != nil { + if err != nil { + return nil, err + } + + if status != nil { s.cacheStatus(uri, status) } - return status, err + return s.getAttachedStatuses(ctx, status), err } func (s *statusDB) PutStatus(ctx context.Context, status *gtsmodel.Status) db.Error { diff --git a/internal/db/pg/status_test.go b/internal/db/pg/status_test.go index 05384bdd6..e3b9f1867 100644 --- a/internal/db/pg/status_test.go +++ b/internal/db/pg/status_test.go @@ -69,7 +69,6 @@ func (suite *StatusTestSuite) TestGetStatusByID() { suite.Nil(status.BoostOfAccount) suite.Nil(status.InReplyTo) suite.Nil(status.InReplyToAccount) - suite.log.Debug("test finished") } func (suite *StatusTestSuite) TestGetStatusByURI() { @@ -119,14 +118,14 @@ func (suite *StatusTestSuite) TestGetStatusTwice() { suite.NoError(err) after1 := time.Now() duration1 := after1.Sub(before1) - fmt.Println(duration1.Nanoseconds()) + fmt.Println(duration1.Milliseconds()) before2 := time.Now() _, err = suite.db.GetStatusByURI(context.Background(), suite.testStatuses["local_account_1_status_1"].URI) suite.NoError(err) after2 := time.Now() duration2 := after2.Sub(before2) - fmt.Println(duration2.Nanoseconds()) + fmt.Println(duration2.Milliseconds()) // second retrieval should be several orders faster since it will be cached now suite.Less(duration2, duration1) diff --git a/internal/db/pg/timeline.go b/internal/db/pg/timeline.go index e7aebab44..0059f8319 100644 --- a/internal/db/pg/timeline.go +++ b/internal/db/pg/timeline.go @@ -43,21 +43,9 @@ func (t *timelineDB) GetHomeTimeline(ctx context.Context, accountID string, maxI NewSelect(). Model(&statuses) - // Use a WhereGroup here to specify that we want EITHER statuses posted by accounts that accountID follows, - // OR statuses posted by accountID itself (since a user should be able to see their own statuses). - // - // This is equivalent to something like WHERE ... AND (... OR ...) - // See: https://pg.uptrace.dev/queries/#select - whereGroup := func(*bun.SelectQuery) *bun.SelectQuery { - q = q.Where("f.account_id = ?", accountID). - WhereOr("status.account_id = ?", accountID) - return q - } - q = q.ColumnExpr("status.*"). // Find out who accountID follows. Join("LEFT JOIN follows AS f ON f.target_account_id = status.account_id"). - WhereGroup(" AND ", whereGroup). // Sort by highest ID (newest) to lowest ID (oldest) Order("status.id DESC") @@ -86,6 +74,19 @@ func (t *timelineDB) GetHomeTimeline(ctx context.Context, accountID string, maxI q = q.Limit(limit) } + // Use a WhereGroup here to specify that we want EITHER statuses posted by accounts that accountID follows, + // OR statuses posted by accountID itself (since a user should be able to see their own statuses). + // + // This is equivalent to something like WHERE ... AND (... OR ...) + // See: https://pg.uptrace.dev/queries/#select + whereGroup := func(*bun.SelectQuery) *bun.SelectQuery { + return q. + WhereOr("f.account_id = ?", accountID). + WhereOr("status.account_id = ?", accountID) + } + + q = q.WhereGroup(" AND ", whereGroup) + return statuses, processErrorResponse(q.Scan(ctx)) } diff --git a/internal/gtsmodel/account.go b/internal/gtsmodel/account.go index 3c5fe1db4..8eeab1812 100644 --- a/internal/gtsmodel/account.go +++ b/internal/gtsmodel/account.go @@ -45,12 +45,12 @@ type Account struct { */ // ID of the avatar as a media attachment - AvatarMediaAttachmentID string `bun:"type:CHAR(26)"` + AvatarMediaAttachmentID string `bun:"type:CHAR(26),nullzero"` AvatarMediaAttachment *MediaAttachment `bun:"rel:belongs-to"` // For a non-local account, where can the header be fetched? AvatarRemoteURL string // ID of the header as a media attachment - HeaderMediaAttachmentID string `bun:"type:CHAR(26)"` + HeaderMediaAttachmentID string `bun:"type:CHAR(26),nullzero"` HeaderMediaAttachment *MediaAttachment `bun:"rel:belongs-to"` // For a non-local account, where can the header be fetched? HeaderRemoteURL string @@ -63,7 +63,7 @@ type Account struct { // Is this a memorial account, ie., has the user passed away? Memorial bool // This account has moved this account id in the database - MovedToAccountID string `bun:"type:CHAR(26)"` + MovedToAccountID string `bun:"type:CHAR(26),nullzero"` // When was this account created? CreatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"` // When was this account last updated? diff --git a/internal/gtsmodel/block.go b/internal/gtsmodel/block.go index d429ad5dc..0c762837d 100644 --- a/internal/gtsmodel/block.go +++ b/internal/gtsmodel/block.go @@ -7,15 +7,15 @@ type Block struct { // id of this block in the database ID string `bun:"type:CHAR(26),pk,notnull"` // When was this block created - CreatedAt time.Time `bun:"type:timestamp,notnull,default:current_timestamp"` + CreatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"` // When was this block updated - UpdatedAt time.Time `bun:"type:timestamp,notnull,default:current_timestamp"` + UpdatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"` // Who created this block? AccountID string `bun:"type:CHAR(26),notnull"` - Account *Account `bun:"-"` + Account *Account `bun:"rel:belongs-to"` // Who is targeted by this block? TargetAccountID string `bun:"type:CHAR(26),notnull"` - TargetAccount *Account `bun:"-"` + TargetAccount *Account `bun:"rel:belongs-to"` // Activitypub URI for this block URI string `bun:",notnull"` } diff --git a/internal/gtsmodel/domainblock.go b/internal/gtsmodel/domainblock.go index 4462c21f3..03d5ab0af 100644 --- a/internal/gtsmodel/domainblock.go +++ b/internal/gtsmodel/domainblock.go @@ -27,9 +27,9 @@ type DomainBlock struct { // blocked domain Domain string `bun:",pk,notnull,unique"` // When was this block created - CreatedAt time.Time `bun:"type:timestamp,notnull,default:current_timestamp"` + CreatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"` // When was this block updated - UpdatedAt time.Time `bun:"type:timestamp,notnull,default:current_timestamp"` + 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"` @@ -40,5 +40,5 @@ type DomainBlock struct { // 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)"` + SubscriptionID string `bun:"type:CHAR(26),nullzero"` } diff --git a/internal/gtsmodel/emaildomainblock.go b/internal/gtsmodel/emaildomainblock.go index c561c41ee..1919172fa 100644 --- a/internal/gtsmodel/emaildomainblock.go +++ b/internal/gtsmodel/emaildomainblock.go @@ -27,9 +27,9 @@ type EmailDomainBlock struct { // Email domain to block. Eg. 'gmail.com' or 'hotmail.com' Domain string `bun:",notnull"` // When was this block created - CreatedAt time.Time `bun:"type:timestamp,notnull,default:current_timestamp"` + CreatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"` // When was this block updated - UpdatedAt time.Time `bun:"type:timestamp,notnull,default:current_timestamp"` + 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"` diff --git a/internal/gtsmodel/emoji.go b/internal/gtsmodel/emoji.go index beee711e9..549951ddd 100644 --- a/internal/gtsmodel/emoji.go +++ b/internal/gtsmodel/emoji.go @@ -30,9 +30,9 @@ type Emoji struct { // Origin domain of this emoji, eg 'example.org', 'queer.party'. empty string for local emojis. Domain string `bun:",notnull,default:'',unique:shortcodedomain"` // When was this emoji created. Must be unique with shortcode. - CreatedAt time.Time `bun:"type:timestamp,notnull,default:current_timestamp"` + CreatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"` // When was this emoji updated - UpdatedAt time.Time `bun:"type:timestamp,notnull,default:current_timestamp"` + UpdatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"` // Where can this emoji be retrieved remotely? Null for local emojis. // For remote emojis, it'll be something like: // https://hackers.town/system/custom_emojis/images/000/049/842/original/1b74481204feabfd.png @@ -65,7 +65,7 @@ type Emoji struct { // 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:"type:timestamp,notnull,default:current_timestamp"` + 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' @@ -73,6 +73,5 @@ type Emoji struct { // 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)"` - Status *Status `bun:"-"` + CategoryID string `bun:"type:CHAR(26),nullzero"` } diff --git a/internal/gtsmodel/follow.go b/internal/gtsmodel/follow.go index 013cdf5d0..3d3eb1f1b 100644 --- a/internal/gtsmodel/follow.go +++ b/internal/gtsmodel/follow.go @@ -25,15 +25,15 @@ type Follow struct { // id of this follow in the database ID string `bun:"type:CHAR(26),pk,notnull,unique"` // When was this follow created? - CreatedAt time.Time `bun:"type:timestamp,notnull,default:current_timestamp"` + CreatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"` // When was this follow last updated? - UpdatedAt time.Time `bun:"type:timestamp,notnull,default:current_timestamp"` + UpdatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"` // Who does this follow belong to? AccountID string `bun:"type:CHAR(26),unique:srctarget,notnull"` - Account *Account `bun:"-"` + Account *Account `bun:"rel:belongs-to"` // Who does AccountID follow? TargetAccountID string `bun:"type:CHAR(26),unique:srctarget,notnull"` - TargetAccount *Account `bun:"-"` + 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? diff --git a/internal/gtsmodel/followrequest.go b/internal/gtsmodel/followrequest.go index a2dfb7593..4a98525be 100644 --- a/internal/gtsmodel/followrequest.go +++ b/internal/gtsmodel/followrequest.go @@ -25,15 +25,15 @@ type FollowRequest struct { // id of this follow request in the database ID string `bun:"type:CHAR(26),pk,notnull,unique"` // When was this follow request created? - CreatedAt time.Time `bun:"type:timestamp,notnull,default:current_timestamp"` + CreatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"` // When was this follow request last updated? - UpdatedAt time.Time `bun:"type:timestamp,notnull,default:current_timestamp"` + UpdatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"` // Who does this follow request originate from? AccountID string `bun:"type:CHAR(26),unique:frsrctarget,notnull"` - Account Account `bun:"-"` + Account Account `bun:"rel:belongs-to"` // Who is the target of this follow request? TargetAccountID string `bun:"type:CHAR(26),unique:frsrctarget,notnull"` - TargetAccount Account `bun:"-"` + 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? diff --git a/internal/gtsmodel/instance.go b/internal/gtsmodel/instance.go index 9f9a7ce1c..5bfe942f7 100644 --- a/internal/gtsmodel/instance.go +++ b/internal/gtsmodel/instance.go @@ -13,14 +13,14 @@ type Instance struct { // base URI of this instance eg https://example.org URI string `bun:",notnull,unique"` // When was this instance created in the db? - CreatedAt time.Time `bun:"type:timestamp,notnull,default:current_timestamp"` + CreatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"` // When was this instance last updated in the db? - UpdatedAt time.Time `bun:"type:timestamp,notnull,default:current_timestamp"` + UpdatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"` // When was this instance suspended, if at all? - SuspendedAt time.Time + SuspendedAt time.Time `bun:",nullzero"` // ID of any existing domain block for this instance in the database - DomainBlockID string `bun:"type:CHAR(26)"` - DomainBlock *DomainBlock `bun:"-"` + DomainBlockID string `bun:"type:CHAR(26),nullzero"` + DomainBlock *DomainBlock `bun:"rel:belongs-to"` // Short description of this instance ShortDescription string // Longer description of this instance @@ -32,8 +32,8 @@ type Instance struct { // Username of the contact account for this instance ContactAccountUsername string // Contact account ID in the database for this instance - ContactAccountID string `bun:"type:CHAR(26)"` - ContactAccount *Account `bun:"-"` + 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 diff --git a/internal/gtsmodel/mediaattachment.go b/internal/gtsmodel/mediaattachment.go index 86750bcbc..b767e538c 100644 --- a/internal/gtsmodel/mediaattachment.go +++ b/internal/gtsmodel/mediaattachment.go @@ -28,15 +28,15 @@ type MediaAttachment struct { // ID of the attachment in the database ID string `bun:"type:CHAR(26),pk,notnull,unique"` // ID of the status to which this is attached - StatusID string `bun:"type:CHAR(26)"` + StatusID string `bun:"type:CHAR(26),nullzero"` // Where can the attachment be retrieved on *this* server URL string // Where can the attachment be retrieved on a remote server (empty for local media) RemoteURL string // When was the attachment created - CreatedAt time.Time `bun:"type:timestamp,notnull,default:current_timestamp"` + CreatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"` // When was the attachment last updated - UpdatedAt time.Time `bun:"type:timestamp,notnull,default:current_timestamp"` + UpdatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"` // Type of file (image/gif/audio/video) Type FileType `bun:",notnull"` // Metadata about the file @@ -47,7 +47,7 @@ type MediaAttachment struct { // Description of the attachment (for screenreaders) Description string // To which scheduled status does this attachment belong - ScheduledStatusID string `bun:"type:CHAR(26)"` + ScheduledStatusID string `bun:"type:CHAR(26),nullzero"` // What is the generated blurhash of this attachment Blurhash string // What is the processing status of this attachment diff --git a/internal/gtsmodel/mention.go b/internal/gtsmodel/mention.go index d212ed468..ce5977659 100644 --- a/internal/gtsmodel/mention.go +++ b/internal/gtsmodel/mention.go @@ -28,9 +28,9 @@ type Mention struct { StatusID string `bun:"type:CHAR(26),notnull"` Status *Status `bun:"rel:belongs-to"` // When was this mention created? - CreatedAt time.Time `bun:"type:timestamp,notnull,default:current_timestamp"` + CreatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"` // When was this mention last updated? - UpdatedAt time.Time `bun:"type:timestamp,notnull,default:current_timestamp"` + UpdatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"` // What's the internal account ID of the originator of the mention? OriginAccountID string `bun:"type:CHAR(26),notnull"` OriginAccount *Account `bun:"rel:belongs-to"` diff --git a/internal/gtsmodel/messages.go b/internal/gtsmodel/messages.go index 910c74898..62beb0adc 100644 --- a/internal/gtsmodel/messages.go +++ b/internal/gtsmodel/messages.go @@ -1,11 +1,22 @@ -package gtsmodel +/* + GoToSocial + Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org -// // ToClientAPI wraps a message that travels from the processor into the client API -// type ToClientAPI struct { -// APObjectType ActivityStreamsObject -// APActivityType ActivityStreamsActivity -// Activity interface{} -// } + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package gtsmodel // FromClientAPI wraps a message that travels from client API into the processor type FromClientAPI struct { @@ -16,13 +27,6 @@ type FromClientAPI struct { TargetAccount *Account } -// // ToFederator wraps a message that travels from the processor into the federator -// type ToFederator struct { -// APObjectType ActivityStreamsObject -// APActivityType ActivityStreamsActivity -// GTSModel interface{} -// } - // FromFederator wraps a message that travels from the federator into the processor type FromFederator struct { APObjectType string diff --git a/internal/gtsmodel/notification.go b/internal/gtsmodel/notification.go index 9724e9136..14ab90802 100644 --- a/internal/gtsmodel/notification.go +++ b/internal/gtsmodel/notification.go @@ -27,7 +27,7 @@ type Notification struct { // Type of this notification NotificationType NotificationType `bun:",notnull"` // Creation time of this notification - CreatedAt time.Time `bun:"type:timestamp,notnull,default:current_timestamp"` + CreatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"` // Which account does this notification target (ie., who will receive the notification?) TargetAccountID string `bun:"type:CHAR(26),notnull"` TargetAccount *Account `bun:"rel:belongs-to"` @@ -35,7 +35,7 @@ type Notification struct { OriginAccountID string `bun:"type:CHAR(26),notnull"` OriginAccount *Account `bun:"rel:belongs-to"` // If the notification pertains to a status, what is the database ID of that status? - StatusID string `bun:"type:CHAR(26)"` + StatusID string `bun:"type:CHAR(26),nullzero"` Status *Status `bun:"rel:belongs-to"` // Has this notification been read already? Read bool diff --git a/internal/gtsmodel/routersession.go b/internal/gtsmodel/routersession.go index a0b6cd7b6..7f3bd85c3 100644 --- a/internal/gtsmodel/routersession.go +++ b/internal/gtsmodel/routersession.go @@ -21,6 +21,6 @@ package gtsmodel // RouterSession is used to store and retrieve settings for a router session. type RouterSession struct { ID string `bun:"type:CHAR(26),pk,notnull"` - Auth []byte `bun:",notnull"` - Crypt []byte `bun:",notnull"` + Auth []byte `bun:"type:bytea,notnull"` + Crypt []byte `bun:"type:bytea,notnull"` } diff --git a/internal/gtsmodel/status.go b/internal/gtsmodel/status.go index 4ef62dad7..b4772e96b 100644 --- a/internal/gtsmodel/status.go +++ b/internal/gtsmodel/status.go @@ -52,23 +52,23 @@ type Status struct { Local bool // which account posted this status? AccountID string `bun:"type:CHAR(26),notnull"` - Account *Account `bun:"-"` + Account *Account `bun:"rel:belongs-to"` // AP uri of the owner of this status AccountURI string // id of the status this status is a reply to - InReplyToID string `bun:"type:CHAR(26)"` + InReplyToID string `bun:"type:CHAR(26),nullzero"` InReplyTo *Status `bun:"-"` // AP uri of the status this status is a reply to InReplyToURI string // id of the account that this status replies to - InReplyToAccountID string `bun:"type:CHAR(26)"` - InReplyToAccount *Account `bun:"-"` + 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)"` + 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)"` - BoostOfAccount *Account `bun:"-"` + BoostOfAccountID string `bun:"type:CHAR(26),nullzero"` + BoostOfAccount *Account `bun:"rel:belongs-to"` // cw string for this status ContentWarning string // visibility entry for this status @@ -78,8 +78,8 @@ type Status struct { // what language is this status written in? Language string // Which application was used to create this status? - CreatedWithApplicationID string `bun:"type:CHAR(26)"` - CreatedWithApplication *Application `bun:"-"` + 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 diff --git a/internal/gtsmodel/statusbookmark.go b/internal/gtsmodel/statusbookmark.go index 95269a23c..26dafa420 100644 --- a/internal/gtsmodel/statusbookmark.go +++ b/internal/gtsmodel/statusbookmark.go @@ -25,7 +25,7 @@ type StatusBookmark struct { // id of this bookmark in the database ID string `bun:"type:CHAR(26),pk,notnull,unique"` // when was this bookmark created - CreatedAt time.Time `bun:"type:timestamp,notnull,default:current_timestamp"` + CreatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"` // id of the account that created ('did') the bookmarking AccountID string `bun:"type:CHAR(26),notnull"` Account *Account `bun:"rel:belongs-to"` diff --git a/internal/gtsmodel/statusfave.go b/internal/gtsmodel/statusfave.go index 9bd7ae63a..3b816af56 100644 --- a/internal/gtsmodel/statusfave.go +++ b/internal/gtsmodel/statusfave.go @@ -25,7 +25,7 @@ type StatusFave struct { // id of this fave in the database ID string `bun:"type:CHAR(26),pk,notnull,unique"` // when was this fave created - CreatedAt time.Time `bun:"type:timestamp,notnull,default:current_timestamp"` + CreatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"` // id of the account that created ('did') the fave AccountID string `bun:"type:CHAR(26),notnull"` Account *Account `bun:"rel:belongs-to"` diff --git a/internal/gtsmodel/statusmute.go b/internal/gtsmodel/statusmute.go index ef451c928..56a792ab4 100644 --- a/internal/gtsmodel/statusmute.go +++ b/internal/gtsmodel/statusmute.go @@ -25,7 +25,7 @@ type StatusMute struct { // id of this mute in the database ID string `bun:"type:CHAR(26),pk,notnull,unique"` // when was this mute created - CreatedAt time.Time `bun:"type:timestamp,notnull,default:current_timestamp"` + CreatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"` // id of the account that created ('did') the mute AccountID string `bun:"type:CHAR(26),notnull"` Account *Account `bun:"rel:belongs-to"` diff --git a/internal/gtsmodel/tag.go b/internal/gtsmodel/tag.go index c69cc48c2..5006a36f4 100644 --- a/internal/gtsmodel/tag.go +++ b/internal/gtsmodel/tag.go @@ -29,15 +29,15 @@ type Tag struct { // name of this tag -- the tag without the hash part Name string `bun:",unique,notnull"` // Which account ID is the first one we saw using this tag? - FirstSeenFromAccountID string `bun:"type:CHAR(26)"` + FirstSeenFromAccountID string `bun:"type:CHAR(26),nullzero"` // when was this tag created - CreatedAt time.Time `bun:"type:timestamp,notnull,default:current_timestamp"` + CreatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"` // when was this tag last updated - UpdatedAt time.Time `bun:"type:timestamp,notnull,default:current_timestamp"` + 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:"type:timestamp,notnull,default:current_timestamp"` + LastStatusAt time.Time `bun:",nullzero"` } diff --git a/internal/gtsmodel/user.go b/internal/gtsmodel/user.go index dfd8f93d9..026fac9fc 100644 --- a/internal/gtsmodel/user.go +++ b/internal/gtsmodel/user.go @@ -61,7 +61,7 @@ type User struct { // 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)"` + 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? @@ -69,8 +69,8 @@ type User struct { // In what timezone/locale is this user located? Locale string // Which application id created this user? See gtsmodel.Application - CreatedByApplicationID string `bun:"type:CHAR(26)"` - CreatedByApplication *Application `bun:"-"` + 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"` @@ -107,7 +107,7 @@ type User struct { // The generated token that the user can use to reset their password ResetPasswordToken string // When did we email the user their reset-password email? - ResetPasswordSentAt time.Time `bun:"type:timestamp"` + ResetPasswordSentAt time.Time `bun:",nullzero"` EncryptedOTPSecret string EncryptedOTPSecretIv string @@ -117,6 +117,6 @@ type User struct { ConsumedTimestamp int RememberToken string SignInToken string - SignInTokenSentAt time.Time `bun:"type:timestamp"` + SignInTokenSentAt time.Time `bun:",nullzero"` WebauthnID string } diff --git a/internal/processing/account/create.go b/internal/processing/account/create.go index 1eae90d03..37c742b45 100644 --- a/internal/processing/account/create.go +++ b/internal/processing/account/create.go @@ -31,13 +31,21 @@ import ( func (p *processor) Create(ctx context.Context, applicationToken oauth2.TokenInfo, application *gtsmodel.Application, form *apimodel.AccountCreateRequest) (*apimodel.Token, error) { l := p.log.WithField("func", "accountCreate") - if err := p.db.IsEmailAvailable(ctx, form.Email); err != nil { + emailAvailable, err := p.db.IsEmailAvailable(ctx, form.Email) + if err != nil { return nil, err } + if !emailAvailable { + return nil, fmt.Errorf("email address %s in use", form.Email) + } - if err := p.db.IsUsernameAvailable(ctx, form.Username); err != nil { + usernameAvailable, err := p.db.IsUsernameAvailable(ctx, form.Username) + if err != nil { return nil, err } + if !usernameAvailable { + return nil, fmt.Errorf("username %s in use", form.Username) + } // don't store a reason if we don't require one reason := form.Reason