tests passing

This commit is contained in:
tsmethurst 2021-08-24 23:07:14 +02:00
commit ef7e0cfe4f
28 changed files with 168 additions and 146 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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 <http://www.gnu.org/licenses/>.
*/
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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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