mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2025-12-29 15:46:15 -06:00
tests passing
This commit is contained in:
parent
465aa3da86
commit
ef7e0cfe4f
28 changed files with 168 additions and 146 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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!
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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{
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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?
|
||||
|
|
|
|||
|
|
@ -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"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"`
|
||||
|
|
|
|||
|
|
@ -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"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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?
|
||||
|
|
|
|||
|
|
@ -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?
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"`
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"`
|
||||
|
|
|
|||
|
|
@ -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"`
|
||||
|
|
|
|||
|
|
@ -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"`
|
||||
|
|
|
|||
|
|
@ -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"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue