diff options
Diffstat (limited to 'internal/db')
| -rw-r--r-- | internal/db/db.go | 24 | ||||
| -rw-r--r-- | internal/db/pg/pg.go | 256 | ||||
| -rw-r--r-- | internal/db/pg/statuscontext.go | 75 | 
3 files changed, 92 insertions, 263 deletions
| diff --git a/internal/db/db.go b/internal/db/db.go index 51685f024..4e21358c3 100644 --- a/internal/db/db.go +++ b/internal/db/db.go @@ -199,21 +199,6 @@ type DB interface {  	// GetRelationship retrieves the relationship of the targetAccount to the requestingAccount.  	GetRelationship(requestingAccount string, targetAccount string) (*gtsmodel.Relationship, error) -	// StatusVisible returns true if targetStatus is visible to requestingAccount, based on the -	// privacy settings of the status, and any blocks/mutes that might exist between the two accounts -	// or account domains. -	// -	// StatusVisible will also check through the given slice of 'otherRelevantAccounts', which should include: -	// -	// 1. Accounts mentioned in the targetStatus -	// -	// 2. Accounts replied to by the target status -	// -	// 3. Accounts boosted by the target status -	// -	// Will return an error if something goes wrong while pulling stuff out of the database. -	StatusVisible(targetStatus *gtsmodel.Status, requestingAccount *gtsmodel.Account, relevantAccounts *gtsmodel.RelevantAccounts) (bool, error) -  	// Follows returns true if sourceAccount follows target account, or an error if something goes wrong while finding out.  	Follows(sourceAccount *gtsmodel.Account, targetAccount *gtsmodel.Account) (bool, error) @@ -223,9 +208,6 @@ type DB interface {  	// Mutuals returns true if account1 and account2 both follow each other, or an error if something goes wrong while finding out.  	Mutuals(account1 *gtsmodel.Account, account2 *gtsmodel.Account) (bool, error) -	// PullRelevantAccountsFromStatus returns all accounts mentioned in a status, replied to by a status, or boosted by a status -	PullRelevantAccountsFromStatus(status *gtsmodel.Status) (*gtsmodel.RelevantAccounts, error) -  	// GetReplyCountForStatus returns the amount of replies recorded for a status, or an error if something goes wrong  	GetReplyCountForStatus(status *gtsmodel.Status) (int, error) @@ -235,6 +217,12 @@ type DB interface {  	// GetFaveCountForStatus returns the amount of faves/likes recorded for a status, or an error if something goes wrong  	GetFaveCountForStatus(status *gtsmodel.Status) (int, error) +	// StatusParents get the parent statuses of a given status. +	StatusParents(status *gtsmodel.Status) ([]*gtsmodel.Status, error) + +	// StatusChildren gets the child statuses of a given status. +	StatusChildren(status *gtsmodel.Status) ([]*gtsmodel.Status, error) +  	// StatusFavedBy checks if a given status has been faved by a given account ID  	StatusFavedBy(status *gtsmodel.Status, accountID string) (bool, error) diff --git a/internal/db/pg/pg.go b/internal/db/pg/pg.go index 2866a1157..851501334 100644 --- a/internal/db/pg/pg.go +++ b/internal/db/pg/pg.go @@ -806,196 +806,27 @@ func (ps *postgresService) GetRelationship(requestingAccount string, targetAccou  	return r, nil  } -func (ps *postgresService) StatusVisible(targetStatus *gtsmodel.Status, requestingAccount *gtsmodel.Account, relevantAccounts *gtsmodel.RelevantAccounts) (bool, error) { -	l := ps.log.WithField("func", "StatusVisible") - -	targetAccount := relevantAccounts.StatusAuthor - -	// if target account is suspended then don't show the status -	if !targetAccount.SuspendedAt.IsZero() { -		l.Trace("target account suspended at is not zero") -		return false, nil -	} - -	// if the target user doesn't exist (anymore) then the status also shouldn't be visible -	// note: we only do this for local users -	if targetAccount.Domain == "" { -		targetUser := >smodel.User{} -		if err := ps.conn.Model(targetUser).Where("account_id = ?", targetAccount.ID).Select(); err != nil { -			l.Debug("target user could not be selected") -			if err == pg.ErrNoRows { -				return false, db.ErrNoEntries{} -			} -			return false, err -		} - -		// if target user is disabled, not yet approved, or not confirmed then don't show the status -		// (although in the latter two cases it's unlikely they posted a status yet anyway, but you never know!) -		if targetUser.Disabled || !targetUser.Approved || targetUser.ConfirmedAt.IsZero() { -			l.Trace("target user is disabled, not approved, or not confirmed") -			return false, nil -		} -	} - -	// If requesting account is nil, that means whoever requested the status didn't auth, or their auth failed. -	// In this case, we can still serve the status if it's public, otherwise we definitely shouldn't. -	if requestingAccount == nil { -		if targetStatus.Visibility == gtsmodel.VisibilityPublic { -			return true, nil -		} -		l.Trace("requesting account is nil but the target status isn't public") -		return false, nil -	} - -	// if requesting account is suspended then don't show the status -- although they probably shouldn't have gotten -	// this far (ie., been authed) in the first place: this is just for safety. -	if !requestingAccount.SuspendedAt.IsZero() { -		l.Trace("requesting account is suspended") -		return false, nil -	} - -	// check if we have a local account -- if so we can check the user for that account in the DB -	if requestingAccount.Domain == "" { -		requestingUser := >smodel.User{} -		if err := ps.conn.Model(requestingUser).Where("account_id = ?", requestingAccount.ID).Select(); err != nil { -			// if the requesting account is local but doesn't have a corresponding user in the db this is a problem -			if err == pg.ErrNoRows { -				l.Debug("requesting account is local but there's no corresponding user") -				return false, nil -			} -			l.Debugf("requesting account is local but there was an error getting the corresponding user: %s", err) -			return false, err -		} -		// okay, user exists, so make sure it has full privileges/is confirmed/approved -		if requestingUser.Disabled || !requestingUser.Approved || requestingUser.ConfirmedAt.IsZero() { -			l.Trace("requesting account is local but corresponding user is either disabled, not approved, or not confirmed") -			return false, nil -		} -	} - -	// if the target status belongs to the requesting account, they should always be able to view it at this point -	if targetStatus.AccountID == requestingAccount.ID { -		return true, nil -	} - -	// At this point we have a populated targetAccount, targetStatus, and requestingAccount, so we can check for blocks and whathaveyou -	// First check if a block exists directly between the target account (which authored the status) and the requesting account. -	if blocked, err := ps.Blocked(targetAccount.ID, requestingAccount.ID); err != nil { -		l.Debugf("something went wrong figuring out if the accounts have a block: %s", err) -		return false, err -	} else if blocked { -		// don't allow the status to be viewed if a block exists in *either* direction between these two accounts, no creepy stalking please -		l.Trace("a block exists between requesting account and target account") +func (ps *postgresService) Follows(sourceAccount *gtsmodel.Account, targetAccount *gtsmodel.Account) (bool, error) { +	if sourceAccount == nil || targetAccount == nil {  		return false, nil  	} - -	// check other accounts mentioned/boosted by/replied to by the status, if they exist -	if relevantAccounts != nil { -		// status replies to account id -		if relevantAccounts.ReplyToAccount != nil && relevantAccounts.ReplyToAccount.ID != requestingAccount.ID { -			if blocked, err := ps.Blocked(relevantAccounts.ReplyToAccount.ID, requestingAccount.ID); err != nil { -				return false, err -			} else if blocked { -				l.Trace("a block exists between requesting account and reply to account") -				return false, nil -			} - -			// check reply to ID -			if targetStatus.InReplyToID != "" { -				followsRepliedAccount, err := ps.Follows(requestingAccount, relevantAccounts.ReplyToAccount) -				if err != nil { -					return false, err -				} -				if !followsRepliedAccount { -					l.Trace("target status is a followers-only reply to an account that is not followed by the requesting account") -					return false, nil -				} -			} -		} - -		// status boosts accounts id -		if relevantAccounts.BoostedAccount != nil { -			if blocked, err := ps.Blocked(relevantAccounts.BoostedAccount.ID, requestingAccount.ID); err != nil { -				return false, err -			} else if blocked { -				l.Trace("a block exists between requesting account and boosted account") -				return false, nil -			} -		} - -		// status boosts a reply to account id -		if relevantAccounts.BoostedReplyToAccount != nil { -			if blocked, err := ps.Blocked(relevantAccounts.BoostedReplyToAccount.ID, requestingAccount.ID); err != nil { -				return false, err -			} else if blocked { -				l.Trace("a block exists between requesting account and boosted reply to account") -				return false, nil -			} -		} - -		// status mentions accounts -		for _, a := range relevantAccounts.MentionedAccounts { -			if blocked, err := ps.Blocked(a.ID, requestingAccount.ID); err != nil { -				return false, err -			} else if blocked { -				l.Trace("a block exists between requesting account and a mentioned account") -				return false, nil -			} -		} - -		// if the requesting account is mentioned in the status it should always be visible -		for _, acct := range relevantAccounts.MentionedAccounts { -			if acct.ID == requestingAccount.ID { -				return true, nil // yep it's mentioned! -			} -		} -	} - -	// at this point we know neither account blocks the other, or another account mentioned or otherwise referred to in the status -	// that means it's now just a matter of checking the visibility settings of the status itself -	switch targetStatus.Visibility { -	case gtsmodel.VisibilityPublic, gtsmodel.VisibilityUnlocked: -		// no problem here, just return OK -		return true, nil -	case gtsmodel.VisibilityFollowersOnly: -		// check one-way follow -		follows, err := ps.Follows(requestingAccount, targetAccount) -		if err != nil { -			return false, err -		} -		if !follows { -			l.Trace("requested status is followers only but requesting account is not a follower") -			return false, nil -		} -		return true, nil -	case gtsmodel.VisibilityMutualsOnly: -		// check mutual follow -		mutuals, err := ps.Mutuals(requestingAccount, targetAccount) -		if err != nil { -			return false, err -		} -		if !mutuals { -			l.Trace("requested status is mutuals only but accounts aren't mufos") -			return false, nil -		} -		return true, nil -	case gtsmodel.VisibilityDirect: -		l.Trace("requesting account requests a status it's not mentioned in") -		return false, nil // it's not mentioned -_- -	} - -	return false, errors.New("reached the end of StatusVisible with no result") -} - -func (ps *postgresService) Follows(sourceAccount *gtsmodel.Account, targetAccount *gtsmodel.Account) (bool, error) { +	  	return ps.conn.Model(>smodel.Follow{}).Where("account_id = ?", sourceAccount.ID).Where("target_account_id = ?", targetAccount.ID).Exists()  }  func (ps *postgresService) FollowRequested(sourceAccount *gtsmodel.Account, targetAccount *gtsmodel.Account) (bool, error) { +	if sourceAccount == nil || targetAccount == nil { +		return false, nil +	} +	  	return ps.conn.Model(>smodel.FollowRequest{}).Where("account_id = ?", sourceAccount.ID).Where("target_account_id = ?", targetAccount.ID).Exists()  }  func (ps *postgresService) Mutuals(account1 *gtsmodel.Account, account2 *gtsmodel.Account) (bool, error) { +	if account1 == nil || account2 == nil { +		return false, nil +	} +	  	// make sure account 1 follows account 2  	f1, err := ps.conn.Model(>smodel.Follow{}).Where("account_id = ?", account1.ID).Where("target_account_id = ?", account2.ID).Exists()  	if err != nil { @@ -1017,71 +848,6 @@ func (ps *postgresService) Mutuals(account1 *gtsmodel.Account, account2 *gtsmode  	return f1 && f2, nil  } -func (ps *postgresService) PullRelevantAccountsFromStatus(targetStatus *gtsmodel.Status) (*gtsmodel.RelevantAccounts, error) { -	accounts := >smodel.RelevantAccounts{ -		MentionedAccounts: []*gtsmodel.Account{}, -	} - -	// get the author account -	if targetStatus.GTSAuthorAccount == nil { -		statusAuthor := >smodel.Account{} -		if err := ps.conn.Model(statusAuthor).Where("id = ?", targetStatus.AccountID).Select(); err != nil { -			return accounts, fmt.Errorf("PullRelevantAccountsFromStatus: error getting statusAuthor with id %s: %s", targetStatus.AccountID, err) -		} -		targetStatus.GTSAuthorAccount = statusAuthor -	} -	accounts.StatusAuthor = targetStatus.GTSAuthorAccount - -	// get the replied to account from the status and add it to the pile -	if targetStatus.InReplyToAccountID != "" { -		repliedToAccount := >smodel.Account{} -		if err := ps.conn.Model(repliedToAccount).Where("id = ?", targetStatus.InReplyToAccountID).Select(); err != nil { -			return accounts, fmt.Errorf("PullRelevantAccountsFromStatus: error getting repliedToAcount with id %s: %s", targetStatus.InReplyToAccountID, err) -		} -		accounts.ReplyToAccount = repliedToAccount -	} - -	// get the boosted account from the status and add it to the pile -	if targetStatus.BoostOfID != "" { -		// retrieve the boosted status first -		boostedStatus := >smodel.Status{} -		if err := ps.conn.Model(boostedStatus).Where("id = ?", targetStatus.BoostOfID).Select(); err != nil { -			return accounts, fmt.Errorf("PullRelevantAccountsFromStatus: error getting boostedStatus with id %s: %s", targetStatus.BoostOfID, err) -		} -		boostedAccount := >smodel.Account{} -		if err := ps.conn.Model(boostedAccount).Where("id = ?", boostedStatus.AccountID).Select(); err != nil { -			return accounts, fmt.Errorf("PullRelevantAccountsFromStatus: error getting boostedAccount with id %s: %s", boostedStatus.AccountID, err) -		} -		accounts.BoostedAccount = boostedAccount - -		// the boosted status might be a reply to another account so we should get that too -		if boostedStatus.InReplyToAccountID != "" { -			boostedStatusRepliedToAccount := >smodel.Account{} -			if err := ps.conn.Model(boostedStatusRepliedToAccount).Where("id = ?", boostedStatus.InReplyToAccountID).Select(); err != nil { -				return accounts, fmt.Errorf("PullRelevantAccountsFromStatus: error getting boostedStatusRepliedToAccount with id %s: %s", boostedStatus.InReplyToAccountID, err) -			} -			accounts.BoostedReplyToAccount = boostedStatusRepliedToAccount -		} -	} - -	// now get all accounts with IDs that are mentioned in the status -	for _, mentionID := range targetStatus.Mentions { - -		mention := >smodel.Mention{} -		if err := ps.conn.Model(mention).Where("id = ?", mentionID).Select(); err != nil { -			return accounts, fmt.Errorf("PullRelevantAccountsFromStatus: error getting mention with id %s: %s", mentionID, err) -		} - -		mentionedAccount := >smodel.Account{} -		if err := ps.conn.Model(mentionedAccount).Where("id = ?", mention.TargetAccountID).Select(); err != nil { -			return accounts, fmt.Errorf("PullRelevantAccountsFromStatus: error getting mentioned account: %s", err) -		} -		accounts.MentionedAccounts = append(accounts.MentionedAccounts, mentionedAccount) -	} - -	return accounts, nil -} -  func (ps *postgresService) GetReplyCountForStatus(status *gtsmodel.Status) (int, error) {  	return ps.conn.Model(>smodel.Status{}).Where("in_reply_to_id = ?", status.ID).Count()  } diff --git a/internal/db/pg/statuscontext.go b/internal/db/pg/statuscontext.go new file mode 100644 index 000000000..e907a2d6f --- /dev/null +++ b/internal/db/pg/statuscontext.go @@ -0,0 +1,75 @@ +package pg + +import ( +	"container/list" +	"errors" + +	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" +) + +func (ps *postgresService) StatusParents(status *gtsmodel.Status) ([]*gtsmodel.Status, error) { +	parents := []*gtsmodel.Status{} +	ps.statusParent(status, &parents) + +	return parents, nil +} + +func (ps *postgresService) statusParent(status *gtsmodel.Status, foundStatuses *[]*gtsmodel.Status) { +	if status.InReplyToID == "" { +		return +	} + +	parentStatus := >smodel.Status{} +	if err := ps.conn.Model(parentStatus).Where("id = ?", status.InReplyToID).Select(); err == nil { +		*foundStatuses = append(*foundStatuses, parentStatus) +	} + +	ps.statusParent(parentStatus, foundStatuses) +} + +func (ps *postgresService) StatusChildren(status *gtsmodel.Status) ([]*gtsmodel.Status, error) { +	foundStatuses := &list.List{} +	foundStatuses.PushFront(status) +	ps.statusChildren(status, foundStatuses) + +	children := []*gtsmodel.Status{} +	for e := foundStatuses.Front(); e != nil; e = e.Next() { +		entry, ok := e.Value.(*gtsmodel.Status) +		if !ok { +			panic(errors.New("entry in foundStatuses was not a *gtsmodel.Status")) +		} + +		// only append children, not the overall parent status +		if entry.ID != status.ID { +			children = append(children, entry) +		} +	} + +	return children, nil +} + +func (ps *postgresService) statusChildren(status *gtsmodel.Status, foundStatuses *list.List) { +	immediateChildren := []*gtsmodel.Status{} + +	err := ps.conn.Model(&immediateChildren).Where("in_reply_to_id = ?", status.ID).Select() +	if err != nil { +		return +	} + +	for _, child := range immediateChildren { +	insertLoop: +		for e := foundStatuses.Front(); e != nil; e = e.Next() { +			entry, ok := e.Value.(*gtsmodel.Status) +			if !ok { +				panic(errors.New("entry in foundStatuses was not a *gtsmodel.Status")) +			} + +			if child.InReplyToAccountID != "" && entry.ID == child.InReplyToID { +				foundStatuses.InsertAfter(child, e) +				break insertLoop +			} +		} + +		ps.statusChildren(child, foundStatuses) +	} +} | 
