mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2024-10-31 22:40:01 +00:00
[bugfix] Lock when checking/creating notifs to avoid race (#2890)
* [bugfix] Lock when checking/creating notifs to avoid race * test notif spam
This commit is contained in:
parent
725a21b027
commit
ebec95a522
15 changed files with 290 additions and 133 deletions
|
@ -113,7 +113,7 @@ func (p *Processor) MoveSelf(
|
||||||
// in quick succession, so get a lock on
|
// in quick succession, so get a lock on
|
||||||
// this account.
|
// this account.
|
||||||
lockKey := originAcct.URI
|
lockKey := originAcct.URI
|
||||||
unlock := p.state.AccountLocks.Lock(lockKey)
|
unlock := p.state.ProcessingLocks.Lock(lockKey)
|
||||||
defer unlock()
|
defer unlock()
|
||||||
|
|
||||||
// Ensure we have a valid, up-to-date representation of the target account.
|
// Ensure we have a valid, up-to-date representation of the target account.
|
||||||
|
|
|
@ -49,7 +49,7 @@ func (p *Processor) AccountApprove(
|
||||||
// Get a lock on the account URI,
|
// Get a lock on the account URI,
|
||||||
// to ensure it's not also being
|
// to ensure it's not also being
|
||||||
// rejected at the same time!
|
// rejected at the same time!
|
||||||
unlock := p.state.AccountLocks.Lock(user.Account.URI)
|
unlock := p.state.ProcessingLocks.Lock(user.Account.URI)
|
||||||
defer unlock()
|
defer unlock()
|
||||||
|
|
||||||
if !*user.Approved {
|
if !*user.Approved {
|
||||||
|
|
|
@ -52,7 +52,7 @@ func (p *Processor) AccountReject(
|
||||||
// Get a lock on the account URI,
|
// Get a lock on the account URI,
|
||||||
// since we're going to be deleting
|
// since we're going to be deleting
|
||||||
// it and its associated user.
|
// it and its associated user.
|
||||||
unlock := p.state.AccountLocks.Lock(user.Account.URI)
|
unlock := p.state.ProcessingLocks.Lock(user.Account.URI)
|
||||||
defer unlock()
|
defer unlock()
|
||||||
|
|
||||||
// Can't reject an account with a
|
// Can't reject an account with a
|
||||||
|
|
|
@ -83,7 +83,7 @@ func (p *Processor) PinCreate(ctx context.Context, requestingAccount *gtsmodel.A
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get a lock on this account.
|
// Get a lock on this account.
|
||||||
unlock := p.state.AccountLocks.Lock(requestingAccount.URI)
|
unlock := p.state.ProcessingLocks.Lock(requestingAccount.URI)
|
||||||
defer unlock()
|
defer unlock()
|
||||||
|
|
||||||
if !targetStatus.PinnedAt.IsZero() {
|
if !targetStatus.PinnedAt.IsZero() {
|
||||||
|
@ -148,7 +148,7 @@ func (p *Processor) PinRemove(ctx context.Context, requestingAccount *gtsmodel.A
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get a lock on this account.
|
// Get a lock on this account.
|
||||||
unlock := p.state.AccountLocks.Lock(requestingAccount.URI)
|
unlock := p.state.ProcessingLocks.Lock(requestingAccount.URI)
|
||||||
defer unlock()
|
defer unlock()
|
||||||
|
|
||||||
if targetStatus.PinnedAt.IsZero() {
|
if targetStatus.PinnedAt.IsZero() {
|
||||||
|
|
|
@ -41,7 +41,7 @@
|
||||||
type clientAPI struct {
|
type clientAPI struct {
|
||||||
state *state.State
|
state *state.State
|
||||||
converter *typeutils.Converter
|
converter *typeutils.Converter
|
||||||
surface *surface
|
surface *Surface
|
||||||
federate *federate
|
federate *federate
|
||||||
account *account.Processor
|
account *account.Processor
|
||||||
utils *utils
|
utils *utils
|
||||||
|
|
|
@ -41,7 +41,7 @@
|
||||||
// from the federation/ActivityPub API.
|
// from the federation/ActivityPub API.
|
||||||
type fediAPI struct {
|
type fediAPI struct {
|
||||||
state *state.State
|
state *state.State
|
||||||
surface *surface
|
surface *Surface
|
||||||
federate *federate
|
federate *federate
|
||||||
account *account.Processor
|
account *account.Processor
|
||||||
utils *utils
|
utils *utils
|
||||||
|
|
|
@ -25,16 +25,16 @@
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/typeutils"
|
"github.com/superseriousbusiness/gotosocial/internal/typeutils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// surface wraps functions for 'surfacing' the result
|
// Surface wraps functions for 'surfacing' the result
|
||||||
// of processing a message, eg:
|
// of processing a message, eg:
|
||||||
// - timelining a status
|
// - timelining a status
|
||||||
// - removing a status from timelines
|
// - removing a status from timelines
|
||||||
// - sending a notification to a user
|
// - sending a notification to a user
|
||||||
// - sending an email
|
// - sending an email
|
||||||
type surface struct {
|
type Surface struct {
|
||||||
state *state.State
|
State *state.State
|
||||||
converter *typeutils.Converter
|
Converter *typeutils.Converter
|
||||||
stream *stream.Processor
|
Stream *stream.Processor
|
||||||
filter *visibility.Filter
|
Filter *visibility.Filter
|
||||||
emailSender email.Sender
|
EmailSender email.Sender
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,8 +33,8 @@
|
||||||
|
|
||||||
// emailUserReportClosed emails the user who created the
|
// emailUserReportClosed emails the user who created the
|
||||||
// given report, to inform them the report has been closed.
|
// given report, to inform them the report has been closed.
|
||||||
func (s *surface) emailUserReportClosed(ctx context.Context, report *gtsmodel.Report) error {
|
func (s *Surface) emailUserReportClosed(ctx context.Context, report *gtsmodel.Report) error {
|
||||||
user, err := s.state.DB.GetUserByAccountID(ctx, report.Account.ID)
|
user, err := s.State.DB.GetUserByAccountID(ctx, report.Account.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return gtserror.Newf("db error getting user: %w", err)
|
return gtserror.Newf("db error getting user: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -51,12 +51,12 @@ func (s *surface) emailUserReportClosed(ctx context.Context, report *gtsmodel.Re
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
instance, err := s.state.DB.GetInstance(ctx, config.GetHost())
|
instance, err := s.State.DB.GetInstance(ctx, config.GetHost())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return gtserror.Newf("db error getting instance: %w", err)
|
return gtserror.Newf("db error getting instance: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.state.DB.PopulateReport(ctx, report); err != nil {
|
if err := s.State.DB.PopulateReport(ctx, report); err != nil {
|
||||||
return gtserror.Newf("error populating report: %w", err)
|
return gtserror.Newf("error populating report: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,12 +69,12 @@ func (s *surface) emailUserReportClosed(ctx context.Context, report *gtsmodel.Re
|
||||||
ActionTakenComment: report.ActionTaken,
|
ActionTakenComment: report.ActionTaken,
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.emailSender.SendReportClosedEmail(user.Email, reportClosedData)
|
return s.EmailSender.SendReportClosedEmail(user.Email, reportClosedData)
|
||||||
}
|
}
|
||||||
|
|
||||||
// emailUserPleaseConfirm emails the given user
|
// emailUserPleaseConfirm emails the given user
|
||||||
// to ask them to confirm their email address.
|
// to ask them to confirm their email address.
|
||||||
func (s *surface) emailUserPleaseConfirm(ctx context.Context, user *gtsmodel.User) error {
|
func (s *Surface) emailUserPleaseConfirm(ctx context.Context, user *gtsmodel.User) error {
|
||||||
if user.UnconfirmedEmail == "" ||
|
if user.UnconfirmedEmail == "" ||
|
||||||
user.UnconfirmedEmail == user.Email {
|
user.UnconfirmedEmail == user.Email {
|
||||||
// User has already confirmed this
|
// User has already confirmed this
|
||||||
|
@ -82,7 +82,7 @@ func (s *surface) emailUserPleaseConfirm(ctx context.Context, user *gtsmodel.Use
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
instance, err := s.state.DB.GetInstance(ctx, config.GetHost())
|
instance, err := s.State.DB.GetInstance(ctx, config.GetHost())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return gtserror.Newf("db error getting instance: %w", err)
|
return gtserror.Newf("db error getting instance: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -97,7 +97,7 @@ func (s *surface) emailUserPleaseConfirm(ctx context.Context, user *gtsmodel.Use
|
||||||
)
|
)
|
||||||
|
|
||||||
// Assemble email contents and send the email.
|
// Assemble email contents and send the email.
|
||||||
if err := s.emailSender.SendConfirmEmail(
|
if err := s.EmailSender.SendConfirmEmail(
|
||||||
user.UnconfirmedEmail,
|
user.UnconfirmedEmail,
|
||||||
email.ConfirmData{
|
email.ConfirmData{
|
||||||
Username: user.Account.Username,
|
Username: user.Account.Username,
|
||||||
|
@ -116,7 +116,7 @@ func (s *surface) emailUserPleaseConfirm(ctx context.Context, user *gtsmodel.Use
|
||||||
user.ConfirmationSentAt = now
|
user.ConfirmationSentAt = now
|
||||||
user.LastEmailedAt = now
|
user.LastEmailedAt = now
|
||||||
|
|
||||||
if err := s.state.DB.UpdateUser(
|
if err := s.State.DB.UpdateUser(
|
||||||
ctx,
|
ctx,
|
||||||
user,
|
user,
|
||||||
"confirmation_token",
|
"confirmation_token",
|
||||||
|
@ -131,7 +131,7 @@ func (s *surface) emailUserPleaseConfirm(ctx context.Context, user *gtsmodel.Use
|
||||||
|
|
||||||
// emailUserSignupApproved emails the given user
|
// emailUserSignupApproved emails the given user
|
||||||
// to inform them their sign-up has been approved.
|
// to inform them their sign-up has been approved.
|
||||||
func (s *surface) emailUserSignupApproved(ctx context.Context, user *gtsmodel.User) error {
|
func (s *Surface) emailUserSignupApproved(ctx context.Context, user *gtsmodel.User) error {
|
||||||
// User may have been approved without
|
// User may have been approved without
|
||||||
// their email address being confirmed
|
// their email address being confirmed
|
||||||
// yet. Just send to whatever we have.
|
// yet. Just send to whatever we have.
|
||||||
|
@ -140,13 +140,13 @@ func (s *surface) emailUserSignupApproved(ctx context.Context, user *gtsmodel.Us
|
||||||
emailAddr = user.UnconfirmedEmail
|
emailAddr = user.UnconfirmedEmail
|
||||||
}
|
}
|
||||||
|
|
||||||
instance, err := s.state.DB.GetInstance(ctx, config.GetHost())
|
instance, err := s.State.DB.GetInstance(ctx, config.GetHost())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return gtserror.Newf("db error getting instance: %w", err)
|
return gtserror.Newf("db error getting instance: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assemble email contents and send the email.
|
// Assemble email contents and send the email.
|
||||||
if err := s.emailSender.SendSignupApprovedEmail(
|
if err := s.EmailSender.SendSignupApprovedEmail(
|
||||||
emailAddr,
|
emailAddr,
|
||||||
email.SignupApprovedData{
|
email.SignupApprovedData{
|
||||||
Username: user.Account.Username,
|
Username: user.Account.Username,
|
||||||
|
@ -162,7 +162,7 @@ func (s *surface) emailUserSignupApproved(ctx context.Context, user *gtsmodel.Us
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
user.LastEmailedAt = now
|
user.LastEmailedAt = now
|
||||||
|
|
||||||
if err := s.state.DB.UpdateUser(
|
if err := s.State.DB.UpdateUser(
|
||||||
ctx,
|
ctx,
|
||||||
user,
|
user,
|
||||||
"last_emailed_at",
|
"last_emailed_at",
|
||||||
|
@ -175,14 +175,14 @@ func (s *surface) emailUserSignupApproved(ctx context.Context, user *gtsmodel.Us
|
||||||
|
|
||||||
// emailUserSignupApproved emails the given user
|
// emailUserSignupApproved emails the given user
|
||||||
// to inform them their sign-up has been approved.
|
// to inform them their sign-up has been approved.
|
||||||
func (s *surface) emailUserSignupRejected(ctx context.Context, deniedUser *gtsmodel.DeniedUser) error {
|
func (s *Surface) emailUserSignupRejected(ctx context.Context, deniedUser *gtsmodel.DeniedUser) error {
|
||||||
instance, err := s.state.DB.GetInstance(ctx, config.GetHost())
|
instance, err := s.State.DB.GetInstance(ctx, config.GetHost())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return gtserror.Newf("db error getting instance: %w", err)
|
return gtserror.Newf("db error getting instance: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assemble email contents and send the email.
|
// Assemble email contents and send the email.
|
||||||
return s.emailSender.SendSignupRejectedEmail(
|
return s.EmailSender.SendSignupRejectedEmail(
|
||||||
deniedUser.Email,
|
deniedUser.Email,
|
||||||
email.SignupRejectedData{
|
email.SignupRejectedData{
|
||||||
Message: deniedUser.Message,
|
Message: deniedUser.Message,
|
||||||
|
@ -194,13 +194,13 @@ func (s *surface) emailUserSignupRejected(ctx context.Context, deniedUser *gtsmo
|
||||||
|
|
||||||
// emailAdminReportOpened emails all active moderators/admins
|
// emailAdminReportOpened emails all active moderators/admins
|
||||||
// of this instance that a new report has been created.
|
// of this instance that a new report has been created.
|
||||||
func (s *surface) emailAdminReportOpened(ctx context.Context, report *gtsmodel.Report) error {
|
func (s *Surface) emailAdminReportOpened(ctx context.Context, report *gtsmodel.Report) error {
|
||||||
instance, err := s.state.DB.GetInstance(ctx, config.GetHost())
|
instance, err := s.State.DB.GetInstance(ctx, config.GetHost())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return gtserror.Newf("error getting instance: %w", err)
|
return gtserror.Newf("error getting instance: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
toAddresses, err := s.state.DB.GetInstanceModeratorAddresses(ctx)
|
toAddresses, err := s.State.DB.GetInstanceModeratorAddresses(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, db.ErrNoEntries) {
|
if errors.Is(err, db.ErrNoEntries) {
|
||||||
// No registered moderator addresses.
|
// No registered moderator addresses.
|
||||||
|
@ -209,7 +209,7 @@ func (s *surface) emailAdminReportOpened(ctx context.Context, report *gtsmodel.R
|
||||||
return gtserror.Newf("error getting instance moderator addresses: %w", err)
|
return gtserror.Newf("error getting instance moderator addresses: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.state.DB.PopulateReport(ctx, report); err != nil {
|
if err := s.State.DB.PopulateReport(ctx, report); err != nil {
|
||||||
return gtserror.Newf("error populating report: %w", err)
|
return gtserror.Newf("error populating report: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -221,7 +221,7 @@ func (s *surface) emailAdminReportOpened(ctx context.Context, report *gtsmodel.R
|
||||||
ReportTargetDomain: report.TargetAccount.Domain,
|
ReportTargetDomain: report.TargetAccount.Domain,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.emailSender.SendNewReportEmail(toAddresses, reportData); err != nil {
|
if err := s.EmailSender.SendNewReportEmail(toAddresses, reportData); err != nil {
|
||||||
return gtserror.Newf("error emailing instance moderators: %w", err)
|
return gtserror.Newf("error emailing instance moderators: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -230,13 +230,13 @@ func (s *surface) emailAdminReportOpened(ctx context.Context, report *gtsmodel.R
|
||||||
|
|
||||||
// emailAdminNewSignup emails all active moderators/admins of this
|
// emailAdminNewSignup emails all active moderators/admins of this
|
||||||
// instance that a new account sign-up has been submitted to the instance.
|
// instance that a new account sign-up has been submitted to the instance.
|
||||||
func (s *surface) emailAdminNewSignup(ctx context.Context, newUser *gtsmodel.User) error {
|
func (s *Surface) emailAdminNewSignup(ctx context.Context, newUser *gtsmodel.User) error {
|
||||||
instance, err := s.state.DB.GetInstance(ctx, config.GetHost())
|
instance, err := s.State.DB.GetInstance(ctx, config.GetHost())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return gtserror.Newf("error getting instance: %w", err)
|
return gtserror.Newf("error getting instance: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
toAddresses, err := s.state.DB.GetInstanceModeratorAddresses(ctx)
|
toAddresses, err := s.State.DB.GetInstanceModeratorAddresses(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, db.ErrNoEntries) {
|
if errors.Is(err, db.ErrNoEntries) {
|
||||||
// No registered moderator addresses.
|
// No registered moderator addresses.
|
||||||
|
@ -246,7 +246,7 @@ func (s *surface) emailAdminNewSignup(ctx context.Context, newUser *gtsmodel.Use
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure user populated.
|
// Ensure user populated.
|
||||||
if err := s.state.DB.PopulateUser(ctx, newUser); err != nil {
|
if err := s.State.DB.PopulateUser(ctx, newUser); err != nil {
|
||||||
return gtserror.Newf("error populating user: %w", err)
|
return gtserror.Newf("error populating user: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -259,7 +259,7 @@ func (s *surface) emailAdminNewSignup(ctx context.Context, newUser *gtsmodel.Use
|
||||||
SignupURL: instance.URI + "/settings/admin/accounts/" + newUser.AccountID,
|
SignupURL: instance.URI + "/settings/admin/accounts/" + newUser.AccountID,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.emailSender.SendNewSignupEmail(toAddresses, newSignupData); err != nil {
|
if err := s.EmailSender.SendNewSignupEmail(toAddresses, newSignupData); err != nil {
|
||||||
return gtserror.Newf("error emailing instance moderators: %w", err)
|
return gtserror.Newf("error emailing instance moderators: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,18 +20,20 @@
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/gtscontext"
|
"github.com/superseriousbusiness/gotosocial/internal/gtscontext"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/id"
|
"github.com/superseriousbusiness/gotosocial/internal/id"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// notifyMentions iterates through mentions on the
|
// notifyMentions iterates through mentions on the
|
||||||
// given status, and notifies each mentioned account
|
// given status, and notifies each mentioned account
|
||||||
// that they have a new mention.
|
// that they have a new mention.
|
||||||
func (s *surface) notifyMentions(
|
func (s *Surface) notifyMentions(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
status *gtsmodel.Status,
|
status *gtsmodel.Status,
|
||||||
) error {
|
) error {
|
||||||
|
@ -43,7 +45,7 @@ func (s *surface) notifyMentions(
|
||||||
mention.Status = status
|
mention.Status = status
|
||||||
|
|
||||||
// Beforehand, ensure the passed mention is fully populated.
|
// Beforehand, ensure the passed mention is fully populated.
|
||||||
if err := s.state.DB.PopulateMention(ctx, mention); err != nil {
|
if err := s.State.DB.PopulateMention(ctx, mention); err != nil {
|
||||||
errs.Appendf("error populating mention %s: %w", mention.ID, err)
|
errs.Appendf("error populating mention %s: %w", mention.ID, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -56,7 +58,7 @@ func (s *surface) notifyMentions(
|
||||||
|
|
||||||
// Ensure thread not muted
|
// Ensure thread not muted
|
||||||
// by mentioned account.
|
// by mentioned account.
|
||||||
muted, err := s.state.DB.IsThreadMutedByAccount(
|
muted, err := s.State.DB.IsThreadMutedByAccount(
|
||||||
ctx,
|
ctx,
|
||||||
status.ThreadID,
|
status.ThreadID,
|
||||||
mention.TargetAccountID,
|
mention.TargetAccountID,
|
||||||
|
@ -75,7 +77,7 @@ func (s *surface) notifyMentions(
|
||||||
|
|
||||||
// notify mentioned
|
// notify mentioned
|
||||||
// by status author.
|
// by status author.
|
||||||
if err := s.notify(ctx,
|
if err := s.Notify(ctx,
|
||||||
gtsmodel.NotificationMention,
|
gtsmodel.NotificationMention,
|
||||||
mention.TargetAccount,
|
mention.TargetAccount,
|
||||||
mention.OriginAccount,
|
mention.OriginAccount,
|
||||||
|
@ -91,12 +93,12 @@ func (s *surface) notifyMentions(
|
||||||
|
|
||||||
// notifyFollowRequest notifies the target of the given
|
// notifyFollowRequest notifies the target of the given
|
||||||
// follow request that they have a new follow request.
|
// follow request that they have a new follow request.
|
||||||
func (s *surface) notifyFollowRequest(
|
func (s *Surface) notifyFollowRequest(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
followReq *gtsmodel.FollowRequest,
|
followReq *gtsmodel.FollowRequest,
|
||||||
) error {
|
) error {
|
||||||
// Beforehand, ensure the passed follow request is fully populated.
|
// Beforehand, ensure the passed follow request is fully populated.
|
||||||
if err := s.state.DB.PopulateFollowRequest(ctx, followReq); err != nil {
|
if err := s.State.DB.PopulateFollowRequest(ctx, followReq); err != nil {
|
||||||
return gtserror.Newf("error populating follow request %s: %w", followReq.ID, err)
|
return gtserror.Newf("error populating follow request %s: %w", followReq.ID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,7 +109,7 @@ func (s *surface) notifyFollowRequest(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now notify the follow request itself.
|
// Now notify the follow request itself.
|
||||||
if err := s.notify(ctx,
|
if err := s.Notify(ctx,
|
||||||
gtsmodel.NotificationFollowRequest,
|
gtsmodel.NotificationFollowRequest,
|
||||||
followReq.TargetAccount,
|
followReq.TargetAccount,
|
||||||
followReq.Account,
|
followReq.Account,
|
||||||
|
@ -123,12 +125,12 @@ func (s *surface) notifyFollowRequest(
|
||||||
// they have a new follow. It will also remove any previous
|
// they have a new follow. It will also remove any previous
|
||||||
// notification of a follow request, essentially replacing
|
// notification of a follow request, essentially replacing
|
||||||
// that notification.
|
// that notification.
|
||||||
func (s *surface) notifyFollow(
|
func (s *Surface) notifyFollow(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
follow *gtsmodel.Follow,
|
follow *gtsmodel.Follow,
|
||||||
) error {
|
) error {
|
||||||
// Beforehand, ensure the passed follow is fully populated.
|
// Beforehand, ensure the passed follow is fully populated.
|
||||||
if err := s.state.DB.PopulateFollow(ctx, follow); err != nil {
|
if err := s.State.DB.PopulateFollow(ctx, follow); err != nil {
|
||||||
return gtserror.Newf("error populating follow %s: %w", follow.ID, err)
|
return gtserror.Newf("error populating follow %s: %w", follow.ID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,7 +141,7 @@ func (s *surface) notifyFollow(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if previous follow req notif exists.
|
// Check if previous follow req notif exists.
|
||||||
prevNotif, err := s.state.DB.GetNotification(
|
prevNotif, err := s.State.DB.GetNotification(
|
||||||
gtscontext.SetBarebones(ctx),
|
gtscontext.SetBarebones(ctx),
|
||||||
gtsmodel.NotificationFollowRequest,
|
gtsmodel.NotificationFollowRequest,
|
||||||
follow.TargetAccountID,
|
follow.TargetAccountID,
|
||||||
|
@ -152,14 +154,14 @@ func (s *surface) notifyFollow(
|
||||||
|
|
||||||
if prevNotif != nil {
|
if prevNotif != nil {
|
||||||
// Previous follow request notif existed, delete it before creating new.
|
// Previous follow request notif existed, delete it before creating new.
|
||||||
if err := s.state.DB.DeleteNotificationByID(ctx, prevNotif.ID); // nocollapse
|
if err := s.State.DB.DeleteNotificationByID(ctx, prevNotif.ID); // nocollapse
|
||||||
err != nil && !errors.Is(err, db.ErrNoEntries) {
|
err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||||
return gtserror.Newf("error deleting notification %s: %w", prevNotif.ID, err)
|
return gtserror.Newf("error deleting notification %s: %w", prevNotif.ID, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now notify the follow itself.
|
// Now notify the follow itself.
|
||||||
if err := s.notify(ctx,
|
if err := s.Notify(ctx,
|
||||||
gtsmodel.NotificationFollow,
|
gtsmodel.NotificationFollow,
|
||||||
follow.TargetAccount,
|
follow.TargetAccount,
|
||||||
follow.Account,
|
follow.Account,
|
||||||
|
@ -173,7 +175,7 @@ func (s *surface) notifyFollow(
|
||||||
|
|
||||||
// notifyFave notifies the target of the given
|
// notifyFave notifies the target of the given
|
||||||
// fave that their status has been liked/faved.
|
// fave that their status has been liked/faved.
|
||||||
func (s *surface) notifyFave(
|
func (s *Surface) notifyFave(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
fave *gtsmodel.StatusFave,
|
fave *gtsmodel.StatusFave,
|
||||||
) error {
|
) error {
|
||||||
|
@ -183,7 +185,7 @@ func (s *surface) notifyFave(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Beforehand, ensure the passed status fave is fully populated.
|
// Beforehand, ensure the passed status fave is fully populated.
|
||||||
if err := s.state.DB.PopulateStatusFave(ctx, fave); err != nil {
|
if err := s.State.DB.PopulateStatusFave(ctx, fave); err != nil {
|
||||||
return gtserror.Newf("error populating fave %s: %w", fave.ID, err)
|
return gtserror.Newf("error populating fave %s: %w", fave.ID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -195,7 +197,7 @@ func (s *surface) notifyFave(
|
||||||
|
|
||||||
// Ensure favee hasn't
|
// Ensure favee hasn't
|
||||||
// muted the thread.
|
// muted the thread.
|
||||||
muted, err := s.state.DB.IsThreadMutedByAccount(
|
muted, err := s.State.DB.IsThreadMutedByAccount(
|
||||||
ctx,
|
ctx,
|
||||||
fave.Status.ThreadID,
|
fave.Status.ThreadID,
|
||||||
fave.TargetAccountID,
|
fave.TargetAccountID,
|
||||||
|
@ -212,7 +214,7 @@ func (s *surface) notifyFave(
|
||||||
|
|
||||||
// notify status author
|
// notify status author
|
||||||
// of fave by account.
|
// of fave by account.
|
||||||
if err := s.notify(ctx,
|
if err := s.Notify(ctx,
|
||||||
gtsmodel.NotificationFave,
|
gtsmodel.NotificationFave,
|
||||||
fave.TargetAccount,
|
fave.TargetAccount,
|
||||||
fave.Account,
|
fave.Account,
|
||||||
|
@ -226,7 +228,7 @@ func (s *surface) notifyFave(
|
||||||
|
|
||||||
// notifyAnnounce notifies the status boost target
|
// notifyAnnounce notifies the status boost target
|
||||||
// account that their status has been boosted.
|
// account that their status has been boosted.
|
||||||
func (s *surface) notifyAnnounce(
|
func (s *Surface) notifyAnnounce(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
status *gtsmodel.Status,
|
status *gtsmodel.Status,
|
||||||
) error {
|
) error {
|
||||||
|
@ -241,7 +243,7 @@ func (s *surface) notifyAnnounce(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Beforehand, ensure the passed status is fully populated.
|
// Beforehand, ensure the passed status is fully populated.
|
||||||
if err := s.state.DB.PopulateStatus(ctx, status); err != nil {
|
if err := s.State.DB.PopulateStatus(ctx, status); err != nil {
|
||||||
return gtserror.Newf("error populating status %s: %w", status.ID, err)
|
return gtserror.Newf("error populating status %s: %w", status.ID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -253,7 +255,7 @@ func (s *surface) notifyAnnounce(
|
||||||
|
|
||||||
// Ensure boostee hasn't
|
// Ensure boostee hasn't
|
||||||
// muted the thread.
|
// muted the thread.
|
||||||
muted, err := s.state.DB.IsThreadMutedByAccount(
|
muted, err := s.State.DB.IsThreadMutedByAccount(
|
||||||
ctx,
|
ctx,
|
||||||
status.BoostOf.ThreadID,
|
status.BoostOf.ThreadID,
|
||||||
status.BoostOfAccountID,
|
status.BoostOfAccountID,
|
||||||
|
@ -271,7 +273,7 @@ func (s *surface) notifyAnnounce(
|
||||||
|
|
||||||
// notify status author
|
// notify status author
|
||||||
// of boost by account.
|
// of boost by account.
|
||||||
if err := s.notify(ctx,
|
if err := s.Notify(ctx,
|
||||||
gtsmodel.NotificationReblog,
|
gtsmodel.NotificationReblog,
|
||||||
status.BoostOfAccount,
|
status.BoostOfAccount,
|
||||||
status.Account,
|
status.Account,
|
||||||
|
@ -283,14 +285,14 @@ func (s *surface) notifyAnnounce(
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *surface) notifyPollClose(ctx context.Context, status *gtsmodel.Status) error {
|
func (s *Surface) notifyPollClose(ctx context.Context, status *gtsmodel.Status) error {
|
||||||
// Beforehand, ensure the passed status is fully populated.
|
// Beforehand, ensure the passed status is fully populated.
|
||||||
if err := s.state.DB.PopulateStatus(ctx, status); err != nil {
|
if err := s.State.DB.PopulateStatus(ctx, status); err != nil {
|
||||||
return gtserror.Newf("error populating status %s: %w", status.ID, err)
|
return gtserror.Newf("error populating status %s: %w", status.ID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch all votes in the attached status poll.
|
// Fetch all votes in the attached status poll.
|
||||||
votes, err := s.state.DB.GetPollVotes(ctx, status.PollID)
|
votes, err := s.State.DB.GetPollVotes(ctx, status.PollID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return gtserror.Newf("error getting poll %s votes: %w", status.PollID, err)
|
return gtserror.Newf("error getting poll %s votes: %w", status.PollID, err)
|
||||||
}
|
}
|
||||||
|
@ -300,7 +302,7 @@ func (s *surface) notifyPollClose(ctx context.Context, status *gtsmodel.Status)
|
||||||
if status.Account.IsLocal() {
|
if status.Account.IsLocal() {
|
||||||
// Send a notification to the status
|
// Send a notification to the status
|
||||||
// author that their poll has closed!
|
// author that their poll has closed!
|
||||||
if err := s.notify(ctx,
|
if err := s.Notify(ctx,
|
||||||
gtsmodel.NotificationPoll,
|
gtsmodel.NotificationPoll,
|
||||||
status.Account,
|
status.Account,
|
||||||
status.Account,
|
status.Account,
|
||||||
|
@ -319,7 +321,7 @@ func (s *surface) notifyPollClose(ctx context.Context, status *gtsmodel.Status)
|
||||||
|
|
||||||
// notify voter that
|
// notify voter that
|
||||||
// poll has been closed.
|
// poll has been closed.
|
||||||
if err := s.notify(ctx,
|
if err := s.Notify(ctx,
|
||||||
gtsmodel.NotificationPoll,
|
gtsmodel.NotificationPoll,
|
||||||
vote.Account,
|
vote.Account,
|
||||||
status.Account,
|
status.Account,
|
||||||
|
@ -333,8 +335,8 @@ func (s *surface) notifyPollClose(ctx context.Context, status *gtsmodel.Status)
|
||||||
return errs.Combine()
|
return errs.Combine()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *surface) notifySignup(ctx context.Context, newUser *gtsmodel.User) error {
|
func (s *Surface) notifySignup(ctx context.Context, newUser *gtsmodel.User) error {
|
||||||
modAccounts, err := s.state.DB.GetInstanceModerators(ctx)
|
modAccounts, err := s.State.DB.GetInstanceModerators(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, db.ErrNoEntries) {
|
if errors.Is(err, db.ErrNoEntries) {
|
||||||
// No registered
|
// No registered
|
||||||
|
@ -347,18 +349,18 @@ func (s *surface) notifySignup(ctx context.Context, newUser *gtsmodel.User) erro
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure user + account populated.
|
// Ensure user + account populated.
|
||||||
if err := s.state.DB.PopulateUser(ctx, newUser); err != nil {
|
if err := s.State.DB.PopulateUser(ctx, newUser); err != nil {
|
||||||
return gtserror.Newf("db error populating new user: %w", err)
|
return gtserror.Newf("db error populating new user: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.state.DB.PopulateAccount(ctx, newUser.Account); err != nil {
|
if err := s.State.DB.PopulateAccount(ctx, newUser.Account); err != nil {
|
||||||
return gtserror.Newf("db error populating new user's account: %w", err)
|
return gtserror.Newf("db error populating new user's account: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notify each moderator.
|
// Notify each moderator.
|
||||||
var errs gtserror.MultiError
|
var errs gtserror.MultiError
|
||||||
for _, mod := range modAccounts {
|
for _, mod := range modAccounts {
|
||||||
if err := s.notify(ctx,
|
if err := s.Notify(ctx,
|
||||||
gtsmodel.NotificationSignup,
|
gtsmodel.NotificationSignup,
|
||||||
mod,
|
mod,
|
||||||
newUser.Account,
|
newUser.Account,
|
||||||
|
@ -372,7 +374,24 @@ func (s *surface) notifySignup(ctx context.Context, newUser *gtsmodel.User) erro
|
||||||
return errs.Combine()
|
return errs.Combine()
|
||||||
}
|
}
|
||||||
|
|
||||||
// notify creates, inserts, and streams a new
|
func getNotifyLockURI(
|
||||||
|
notificationType gtsmodel.NotificationType,
|
||||||
|
targetAccount *gtsmodel.Account,
|
||||||
|
originAccount *gtsmodel.Account,
|
||||||
|
statusID string,
|
||||||
|
) string {
|
||||||
|
builder := strings.Builder{}
|
||||||
|
builder.WriteString("notification:?")
|
||||||
|
builder.WriteString("type=" + string(notificationType))
|
||||||
|
builder.WriteString("&target=" + targetAccount.URI)
|
||||||
|
builder.WriteString("&origin=" + originAccount.URI)
|
||||||
|
if statusID != "" {
|
||||||
|
builder.WriteString("&statusID=" + statusID)
|
||||||
|
}
|
||||||
|
return builder.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify creates, inserts, and streams a new
|
||||||
// notification to the target account if it
|
// notification to the target account if it
|
||||||
// doesn't yet exist with the given parameters.
|
// doesn't yet exist with the given parameters.
|
||||||
//
|
//
|
||||||
|
@ -383,7 +402,7 @@ func (s *surface) notifySignup(ctx context.Context, newUser *gtsmodel.User) erro
|
||||||
//
|
//
|
||||||
// targetAccount and originAccount must be
|
// targetAccount and originAccount must be
|
||||||
// set, but statusID can be an empty string.
|
// set, but statusID can be an empty string.
|
||||||
func (s *surface) notify(
|
func (s *Surface) Notify(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
notificationType gtsmodel.NotificationType,
|
notificationType gtsmodel.NotificationType,
|
||||||
targetAccount *gtsmodel.Account,
|
targetAccount *gtsmodel.Account,
|
||||||
|
@ -395,9 +414,24 @@ func (s *surface) notify(
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We're doing state-y stuff so get a
|
||||||
|
// lock on this combo of notif params.
|
||||||
|
lockURI := getNotifyLockURI(
|
||||||
|
notificationType,
|
||||||
|
targetAccount,
|
||||||
|
originAccount,
|
||||||
|
statusID,
|
||||||
|
)
|
||||||
|
unlock := s.State.ProcessingLocks.Lock(lockURI)
|
||||||
|
|
||||||
|
// Wrap the unlock so we
|
||||||
|
// can do granular unlocking.
|
||||||
|
unlock = util.DoOnce(unlock)
|
||||||
|
defer unlock()
|
||||||
|
|
||||||
// Make sure a notification doesn't
|
// Make sure a notification doesn't
|
||||||
// already exist with these params.
|
// already exist with these params.
|
||||||
if _, err := s.state.DB.GetNotification(
|
if _, err := s.State.DB.GetNotification(
|
||||||
gtscontext.SetBarebones(ctx),
|
gtscontext.SetBarebones(ctx),
|
||||||
notificationType,
|
notificationType,
|
||||||
targetAccount.ID,
|
targetAccount.ID,
|
||||||
|
@ -424,16 +458,20 @@ func (s *surface) notify(
|
||||||
StatusID: statusID,
|
StatusID: statusID,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.state.DB.PutNotification(ctx, notif); err != nil {
|
if err := s.State.DB.PutNotification(ctx, notif); err != nil {
|
||||||
return gtserror.Newf("error putting notification in database: %w", err)
|
return gtserror.Newf("error putting notification in database: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Unlock already, we're done
|
||||||
|
// with the state-y stuff.
|
||||||
|
unlock()
|
||||||
|
|
||||||
// Stream notification to the user.
|
// Stream notification to the user.
|
||||||
apiNotif, err := s.converter.NotificationToAPINotification(ctx, notif)
|
apiNotif, err := s.Converter.NotificationToAPINotification(ctx, notif)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return gtserror.Newf("error converting notification to api representation: %w", err)
|
return gtserror.Newf("error converting notification to api representation: %w", err)
|
||||||
}
|
}
|
||||||
s.stream.Notify(ctx, targetAccount, apiNotif)
|
s.Stream.Notify(ctx, targetAccount, apiNotif)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
115
internal/processing/workers/surfacenotify_test.go
Normal file
115
internal/processing/workers/surfacenotify_test.go
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
// GoToSocial
|
||||||
|
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
//
|
||||||
|
// 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 workers_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/filter/visibility"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtscontext"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/processing/workers"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SurfaceNotifyTestSuite struct {
|
||||||
|
WorkersTestSuite
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *SurfaceNotifyTestSuite) TestSpamNotifs() {
|
||||||
|
testStructs := suite.SetupTestStructs()
|
||||||
|
defer suite.TearDownTestStructs(testStructs)
|
||||||
|
|
||||||
|
surface := &workers.Surface{
|
||||||
|
State: testStructs.State,
|
||||||
|
Converter: testStructs.TypeConverter,
|
||||||
|
Stream: testStructs.Processor.Stream(),
|
||||||
|
Filter: visibility.NewFilter(testStructs.State),
|
||||||
|
EmailSender: testStructs.EmailSender,
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
ctx = context.Background()
|
||||||
|
notificationType = gtsmodel.NotificationFollow
|
||||||
|
targetAccount = suite.testAccounts["local_account_1"]
|
||||||
|
originAccount = suite.testAccounts["local_account_2"]
|
||||||
|
)
|
||||||
|
|
||||||
|
// Set up a bunch of goroutines to surface
|
||||||
|
// a notification at exactly the same time.
|
||||||
|
wg := sync.WaitGroup{}
|
||||||
|
wg.Add(20)
|
||||||
|
startAt := time.Now().Add(2 * time.Second)
|
||||||
|
|
||||||
|
for i := 0; i < 20; i++ {
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
// Wait for it....
|
||||||
|
untilTick := time.Until(startAt)
|
||||||
|
<-time.Tick(untilTick)
|
||||||
|
|
||||||
|
// ...Go!
|
||||||
|
if err := surface.Notify(ctx,
|
||||||
|
notificationType,
|
||||||
|
targetAccount,
|
||||||
|
originAccount,
|
||||||
|
"",
|
||||||
|
); err != nil {
|
||||||
|
suite.FailNow(err.Error())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for all notif creation
|
||||||
|
// attempts to complete.
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
// Get all notifs for this account.
|
||||||
|
notifs, err := testStructs.State.DB.GetAccountNotifications(
|
||||||
|
gtscontext.SetBarebones(ctx),
|
||||||
|
targetAccount.ID,
|
||||||
|
"", "", "", 0, nil,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
suite.FailNow(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
var gotOne bool
|
||||||
|
for _, notif := range notifs {
|
||||||
|
if notif.NotificationType == notificationType &&
|
||||||
|
notif.TargetAccountID == targetAccount.ID &&
|
||||||
|
notif.OriginAccountID == originAccount.ID {
|
||||||
|
// This is the notif...
|
||||||
|
if gotOne {
|
||||||
|
// We already had
|
||||||
|
// the notif, d'oh!
|
||||||
|
suite.FailNow("already had notif")
|
||||||
|
} else {
|
||||||
|
gotOne = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSurfaceNotifyTestSuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(SurfaceNotifyTestSuite))
|
||||||
|
}
|
|
@ -36,14 +36,14 @@
|
||||||
// It will also handle notifications for any mentions attached to
|
// It will also handle notifications for any mentions attached to
|
||||||
// the account, and notifications for any local accounts that want
|
// the account, and notifications for any local accounts that want
|
||||||
// to know when this account posts.
|
// to know when this account posts.
|
||||||
func (s *surface) timelineAndNotifyStatus(ctx context.Context, status *gtsmodel.Status) error {
|
func (s *Surface) timelineAndNotifyStatus(ctx context.Context, status *gtsmodel.Status) error {
|
||||||
// Ensure status fully populated; including account, mentions, etc.
|
// Ensure status fully populated; including account, mentions, etc.
|
||||||
if err := s.state.DB.PopulateStatus(ctx, status); err != nil {
|
if err := s.State.DB.PopulateStatus(ctx, status); err != nil {
|
||||||
return gtserror.Newf("error populating status with id %s: %w", status.ID, err)
|
return gtserror.Newf("error populating status with id %s: %w", status.ID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get all local followers of the account that posted the status.
|
// Get all local followers of the account that posted the status.
|
||||||
follows, err := s.state.DB.GetAccountLocalFollowers(ctx, status.AccountID)
|
follows, err := s.State.DB.GetAccountLocalFollowers(ctx, status.AccountID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return gtserror.Newf("error getting local followers of account %s: %w", status.AccountID, err)
|
return gtserror.Newf("error getting local followers of account %s: %w", status.AccountID, err)
|
||||||
}
|
}
|
||||||
|
@ -79,7 +79,7 @@ func (s *surface) timelineAndNotifyStatus(ctx context.Context, status *gtsmodel.
|
||||||
// adding the status to list timelines + home timelines of each
|
// adding the status to list timelines + home timelines of each
|
||||||
// follower, as appropriate, and notifying each follower of the
|
// follower, as appropriate, and notifying each follower of the
|
||||||
// new status, if the status is eligible for notification.
|
// new status, if the status is eligible for notification.
|
||||||
func (s *surface) timelineAndNotifyStatusForFollowers(
|
func (s *Surface) timelineAndNotifyStatusForFollowers(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
status *gtsmodel.Status,
|
status *gtsmodel.Status,
|
||||||
follows []*gtsmodel.Follow,
|
follows []*gtsmodel.Follow,
|
||||||
|
@ -98,7 +98,7 @@ func (s *surface) timelineAndNotifyStatusForFollowers(
|
||||||
// If it's not timelineable, we can just stop early, since lists
|
// If it's not timelineable, we can just stop early, since lists
|
||||||
// are prettymuch subsets of the home timeline, so if it shouldn't
|
// are prettymuch subsets of the home timeline, so if it shouldn't
|
||||||
// appear there, it shouldn't appear in lists either.
|
// appear there, it shouldn't appear in lists either.
|
||||||
timelineable, err := s.filter.StatusHomeTimelineable(
|
timelineable, err := s.Filter.StatusHomeTimelineable(
|
||||||
ctx, follow.Account, status,
|
ctx, follow.Account, status,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -124,7 +124,7 @@ func (s *surface) timelineAndNotifyStatusForFollowers(
|
||||||
// of this follow, if applicable.
|
// of this follow, if applicable.
|
||||||
homeTimelined, err := s.timelineStatus(
|
homeTimelined, err := s.timelineStatus(
|
||||||
ctx,
|
ctx,
|
||||||
s.state.Timelines.Home.IngestOne,
|
s.State.Timelines.Home.IngestOne,
|
||||||
follow.AccountID, // home timelines are keyed by account ID
|
follow.AccountID, // home timelines are keyed by account ID
|
||||||
follow.Account,
|
follow.Account,
|
||||||
status,
|
status,
|
||||||
|
@ -160,7 +160,7 @@ func (s *surface) timelineAndNotifyStatusForFollowers(
|
||||||
// - This is a top-level post (not a reply or boost).
|
// - This is a top-level post (not a reply or boost).
|
||||||
//
|
//
|
||||||
// That means we can officially notify this one.
|
// That means we can officially notify this one.
|
||||||
if err := s.notify(ctx,
|
if err := s.Notify(ctx,
|
||||||
gtsmodel.NotificationStatus,
|
gtsmodel.NotificationStatus,
|
||||||
follow.Account,
|
follow.Account,
|
||||||
status.Account,
|
status.Account,
|
||||||
|
@ -175,7 +175,7 @@ func (s *surface) timelineAndNotifyStatusForFollowers(
|
||||||
|
|
||||||
// listTimelineStatusForFollow puts the given status
|
// listTimelineStatusForFollow puts the given status
|
||||||
// in any eligible lists owned by the given follower.
|
// in any eligible lists owned by the given follower.
|
||||||
func (s *surface) listTimelineStatusForFollow(
|
func (s *Surface) listTimelineStatusForFollow(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
status *gtsmodel.Status,
|
status *gtsmodel.Status,
|
||||||
follow *gtsmodel.Follow,
|
follow *gtsmodel.Follow,
|
||||||
|
@ -189,7 +189,7 @@ func (s *surface) listTimelineStatusForFollow(
|
||||||
// inclusion in the list.
|
// inclusion in the list.
|
||||||
|
|
||||||
// Get every list entry that targets this follow's ID.
|
// Get every list entry that targets this follow's ID.
|
||||||
listEntries, err := s.state.DB.GetListEntriesForFollowID(
|
listEntries, err := s.State.DB.GetListEntriesForFollowID(
|
||||||
// We only need the list IDs.
|
// We only need the list IDs.
|
||||||
gtscontext.SetBarebones(ctx),
|
gtscontext.SetBarebones(ctx),
|
||||||
follow.ID,
|
follow.ID,
|
||||||
|
@ -217,7 +217,7 @@ func (s *surface) listTimelineStatusForFollow(
|
||||||
// list that this list entry belongs to.
|
// list that this list entry belongs to.
|
||||||
if _, err := s.timelineStatus(
|
if _, err := s.timelineStatus(
|
||||||
ctx,
|
ctx,
|
||||||
s.state.Timelines.List.IngestOne,
|
s.State.Timelines.List.IngestOne,
|
||||||
listEntry.ListID, // list timelines are keyed by list ID
|
listEntry.ListID, // list timelines are keyed by list ID
|
||||||
follow.Account,
|
follow.Account,
|
||||||
status,
|
status,
|
||||||
|
@ -232,7 +232,7 @@ func (s *surface) listTimelineStatusForFollow(
|
||||||
// listEligible checks if the given status is eligible
|
// listEligible checks if the given status is eligible
|
||||||
// for inclusion in the list that that the given listEntry
|
// for inclusion in the list that that the given listEntry
|
||||||
// belongs to, based on the replies policy of the list.
|
// belongs to, based on the replies policy of the list.
|
||||||
func (s *surface) listEligible(
|
func (s *Surface) listEligible(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
listEntry *gtsmodel.ListEntry,
|
listEntry *gtsmodel.ListEntry,
|
||||||
status *gtsmodel.Status,
|
status *gtsmodel.Status,
|
||||||
|
@ -253,7 +253,7 @@ func (s *surface) listEligible(
|
||||||
// We need to fetch the list that this
|
// We need to fetch the list that this
|
||||||
// entry belongs to, in order to check
|
// entry belongs to, in order to check
|
||||||
// the list's replies policy.
|
// the list's replies policy.
|
||||||
list, err := s.state.DB.GetListByID(
|
list, err := s.State.DB.GetListByID(
|
||||||
ctx, listEntry.ListID,
|
ctx, listEntry.ListID,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -273,7 +273,7 @@ func (s *surface) listEligible(
|
||||||
//
|
//
|
||||||
// Check if replied-to account is
|
// Check if replied-to account is
|
||||||
// also included in this list.
|
// also included in this list.
|
||||||
includes, err := s.state.DB.ListIncludesAccount(
|
includes, err := s.State.DB.ListIncludesAccount(
|
||||||
ctx,
|
ctx,
|
||||||
list.ID,
|
list.ID,
|
||||||
status.InReplyToAccountID,
|
status.InReplyToAccountID,
|
||||||
|
@ -295,7 +295,7 @@ func (s *surface) listEligible(
|
||||||
//
|
//
|
||||||
// Check if replied-to account is
|
// Check if replied-to account is
|
||||||
// followed by list owner account.
|
// followed by list owner account.
|
||||||
follows, err := s.state.DB.IsFollowing(
|
follows, err := s.State.DB.IsFollowing(
|
||||||
ctx,
|
ctx,
|
||||||
list.AccountID,
|
list.AccountID,
|
||||||
status.InReplyToAccountID,
|
status.InReplyToAccountID,
|
||||||
|
@ -325,7 +325,7 @@ func (s *surface) listEligible(
|
||||||
//
|
//
|
||||||
// If the status was inserted into the timeline, true will be returned
|
// If the status was inserted into the timeline, true will be returned
|
||||||
// + it will also be streamed to the user using the given streamType.
|
// + it will also be streamed to the user using the given streamType.
|
||||||
func (s *surface) timelineStatus(
|
func (s *Surface) timelineStatus(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
ingest func(context.Context, string, timeline.Timelineable) (bool, error),
|
ingest func(context.Context, string, timeline.Timelineable) (bool, error),
|
||||||
timelineID string,
|
timelineID string,
|
||||||
|
@ -343,26 +343,26 @@ func (s *surface) timelineStatus(
|
||||||
}
|
}
|
||||||
|
|
||||||
// The status was inserted so stream it to the user.
|
// The status was inserted so stream it to the user.
|
||||||
apiStatus, err := s.converter.StatusToAPIStatus(ctx, status, account)
|
apiStatus, err := s.Converter.StatusToAPIStatus(ctx, status, account)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = gtserror.Newf("error converting status %s to frontend representation: %w", status.ID, err)
|
err = gtserror.Newf("error converting status %s to frontend representation: %w", status.ID, err)
|
||||||
return true, err
|
return true, err
|
||||||
}
|
}
|
||||||
s.stream.Update(ctx, account, apiStatus, streamType)
|
s.Stream.Update(ctx, account, apiStatus, streamType)
|
||||||
|
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// deleteStatusFromTimelines completely removes the given status from all timelines.
|
// deleteStatusFromTimelines completely removes the given status from all timelines.
|
||||||
// It will also stream deletion of the status to all open streams.
|
// It will also stream deletion of the status to all open streams.
|
||||||
func (s *surface) deleteStatusFromTimelines(ctx context.Context, statusID string) error {
|
func (s *Surface) deleteStatusFromTimelines(ctx context.Context, statusID string) error {
|
||||||
if err := s.state.Timelines.Home.WipeItemFromAllTimelines(ctx, statusID); err != nil {
|
if err := s.State.Timelines.Home.WipeItemFromAllTimelines(ctx, statusID); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := s.state.Timelines.List.WipeItemFromAllTimelines(ctx, statusID); err != nil {
|
if err := s.State.Timelines.List.WipeItemFromAllTimelines(ctx, statusID); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
s.stream.Delete(ctx, statusID)
|
s.Stream.Delete(ctx, statusID)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -370,15 +370,15 @@ func (s *surface) deleteStatusFromTimelines(ctx context.Context, statusID string
|
||||||
// unpreparing it from all timelines, forcing it to be prepared again (with updated
|
// unpreparing it from all timelines, forcing it to be prepared again (with updated
|
||||||
// stats, boost counts, etc) next time it's fetched by the timeline owner. This goes
|
// stats, boost counts, etc) next time it's fetched by the timeline owner. This goes
|
||||||
// both for the status itself, and for any boosts of the status.
|
// both for the status itself, and for any boosts of the status.
|
||||||
func (s *surface) invalidateStatusFromTimelines(ctx context.Context, statusID string) {
|
func (s *Surface) invalidateStatusFromTimelines(ctx context.Context, statusID string) {
|
||||||
if err := s.state.Timelines.Home.UnprepareItemFromAllTimelines(ctx, statusID); err != nil {
|
if err := s.State.Timelines.Home.UnprepareItemFromAllTimelines(ctx, statusID); err != nil {
|
||||||
log.
|
log.
|
||||||
WithContext(ctx).
|
WithContext(ctx).
|
||||||
WithField("statusID", statusID).
|
WithField("statusID", statusID).
|
||||||
Errorf("error unpreparing status from home timelines: %v", err)
|
Errorf("error unpreparing status from home timelines: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.state.Timelines.List.UnprepareItemFromAllTimelines(ctx, statusID); err != nil {
|
if err := s.State.Timelines.List.UnprepareItemFromAllTimelines(ctx, statusID); err != nil {
|
||||||
log.
|
log.
|
||||||
WithContext(ctx).
|
WithContext(ctx).
|
||||||
WithField("statusID", statusID).
|
WithField("statusID", statusID).
|
||||||
|
@ -392,14 +392,14 @@ func (s *surface) invalidateStatusFromTimelines(ctx context.Context, statusID st
|
||||||
// Note that calling invalidateStatusFromTimelines takes care of the
|
// Note that calling invalidateStatusFromTimelines takes care of the
|
||||||
// state in general, we just need to do this for any streams that are
|
// state in general, we just need to do this for any streams that are
|
||||||
// open right now.
|
// open right now.
|
||||||
func (s *surface) timelineStatusUpdate(ctx context.Context, status *gtsmodel.Status) error {
|
func (s *Surface) timelineStatusUpdate(ctx context.Context, status *gtsmodel.Status) error {
|
||||||
// Ensure status fully populated; including account, mentions, etc.
|
// Ensure status fully populated; including account, mentions, etc.
|
||||||
if err := s.state.DB.PopulateStatus(ctx, status); err != nil {
|
if err := s.State.DB.PopulateStatus(ctx, status); err != nil {
|
||||||
return gtserror.Newf("error populating status with id %s: %w", status.ID, err)
|
return gtserror.Newf("error populating status with id %s: %w", status.ID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get all local followers of the account that posted the status.
|
// Get all local followers of the account that posted the status.
|
||||||
follows, err := s.state.DB.GetAccountLocalFollowers(ctx, status.AccountID)
|
follows, err := s.State.DB.GetAccountLocalFollowers(ctx, status.AccountID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return gtserror.Newf("error getting local followers of account %s: %w", status.AccountID, err)
|
return gtserror.Newf("error getting local followers of account %s: %w", status.AccountID, err)
|
||||||
}
|
}
|
||||||
|
@ -427,7 +427,7 @@ func (s *surface) timelineStatusUpdate(ctx context.Context, status *gtsmodel.Sta
|
||||||
// slice of followers of the account that posted the given status,
|
// slice of followers of the account that posted the given status,
|
||||||
// pushing update messages into open list/home streams of each
|
// pushing update messages into open list/home streams of each
|
||||||
// follower.
|
// follower.
|
||||||
func (s *surface) timelineStatusUpdateForFollowers(
|
func (s *Surface) timelineStatusUpdateForFollowers(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
status *gtsmodel.Status,
|
status *gtsmodel.Status,
|
||||||
follows []*gtsmodel.Follow,
|
follows []*gtsmodel.Follow,
|
||||||
|
@ -444,7 +444,7 @@ func (s *surface) timelineStatusUpdateForFollowers(
|
||||||
// If it's not timelineable, we can just stop early, since lists
|
// If it's not timelineable, we can just stop early, since lists
|
||||||
// are prettymuch subsets of the home timeline, so if it shouldn't
|
// are prettymuch subsets of the home timeline, so if it shouldn't
|
||||||
// appear there, it shouldn't appear in lists either.
|
// appear there, it shouldn't appear in lists either.
|
||||||
timelineable, err := s.filter.StatusHomeTimelineable(
|
timelineable, err := s.Filter.StatusHomeTimelineable(
|
||||||
ctx, follow.Account, status,
|
ctx, follow.Account, status,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -485,7 +485,7 @@ func (s *surface) timelineStatusUpdateForFollowers(
|
||||||
|
|
||||||
// listTimelineStatusUpdateForFollow pushes edits of the given status
|
// listTimelineStatusUpdateForFollow pushes edits of the given status
|
||||||
// into any eligible lists streams opened by the given follower.
|
// into any eligible lists streams opened by the given follower.
|
||||||
func (s *surface) listTimelineStatusUpdateForFollow(
|
func (s *Surface) listTimelineStatusUpdateForFollow(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
status *gtsmodel.Status,
|
status *gtsmodel.Status,
|
||||||
follow *gtsmodel.Follow,
|
follow *gtsmodel.Follow,
|
||||||
|
@ -499,7 +499,7 @@ func (s *surface) listTimelineStatusUpdateForFollow(
|
||||||
// inclusion in the list.
|
// inclusion in the list.
|
||||||
|
|
||||||
// Get every list entry that targets this follow's ID.
|
// Get every list entry that targets this follow's ID.
|
||||||
listEntries, err := s.state.DB.GetListEntriesForFollowID(
|
listEntries, err := s.State.DB.GetListEntriesForFollowID(
|
||||||
// We only need the list IDs.
|
// We only need the list IDs.
|
||||||
gtscontext.SetBarebones(ctx),
|
gtscontext.SetBarebones(ctx),
|
||||||
follow.ID,
|
follow.ID,
|
||||||
|
@ -539,17 +539,17 @@ func (s *surface) listTimelineStatusUpdateForFollow(
|
||||||
|
|
||||||
// timelineStatusUpdate streams the edited status to the user using the
|
// timelineStatusUpdate streams the edited status to the user using the
|
||||||
// given streamType.
|
// given streamType.
|
||||||
func (s *surface) timelineStreamStatusUpdate(
|
func (s *Surface) timelineStreamStatusUpdate(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
account *gtsmodel.Account,
|
account *gtsmodel.Account,
|
||||||
status *gtsmodel.Status,
|
status *gtsmodel.Status,
|
||||||
streamType string,
|
streamType string,
|
||||||
) error {
|
) error {
|
||||||
apiStatus, err := s.converter.StatusToAPIStatus(ctx, status, account)
|
apiStatus, err := s.Converter.StatusToAPIStatus(ctx, status, account)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = gtserror.Newf("error converting status %s to frontend representation: %w", status.ID, err)
|
err = gtserror.Newf("error converting status %s to frontend representation: %w", status.ID, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
s.stream.StatusUpdate(ctx, account, apiStatus, streamType)
|
s.Stream.StatusUpdate(ctx, account, apiStatus, streamType)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,7 +38,7 @@ type utils struct {
|
||||||
state *state.State
|
state *state.State
|
||||||
media *media.Processor
|
media *media.Processor
|
||||||
account *account.Processor
|
account *account.Processor
|
||||||
surface *surface
|
surface *Surface
|
||||||
}
|
}
|
||||||
|
|
||||||
// wipeStatus encapsulates common logic
|
// wipeStatus encapsulates common logic
|
||||||
|
@ -245,7 +245,7 @@ func (u *utils) incrementStatusesCount(
|
||||||
status *gtsmodel.Status,
|
status *gtsmodel.Status,
|
||||||
) error {
|
) error {
|
||||||
// Lock on this account since we're changing stats.
|
// Lock on this account since we're changing stats.
|
||||||
unlock := u.state.AccountLocks.Lock(account.URI)
|
unlock := u.state.ProcessingLocks.Lock(account.URI)
|
||||||
defer unlock()
|
defer unlock()
|
||||||
|
|
||||||
// Populate stats.
|
// Populate stats.
|
||||||
|
@ -276,7 +276,7 @@ func (u *utils) decrementStatusesCount(
|
||||||
account *gtsmodel.Account,
|
account *gtsmodel.Account,
|
||||||
) error {
|
) error {
|
||||||
// Lock on this account since we're changing stats.
|
// Lock on this account since we're changing stats.
|
||||||
unlock := u.state.AccountLocks.Lock(account.URI)
|
unlock := u.state.ProcessingLocks.Lock(account.URI)
|
||||||
defer unlock()
|
defer unlock()
|
||||||
|
|
||||||
// Populate stats.
|
// Populate stats.
|
||||||
|
@ -310,7 +310,7 @@ func (u *utils) incrementFollowersCount(
|
||||||
account *gtsmodel.Account,
|
account *gtsmodel.Account,
|
||||||
) error {
|
) error {
|
||||||
// Lock on this account since we're changing stats.
|
// Lock on this account since we're changing stats.
|
||||||
unlock := u.state.AccountLocks.Lock(account.URI)
|
unlock := u.state.ProcessingLocks.Lock(account.URI)
|
||||||
defer unlock()
|
defer unlock()
|
||||||
|
|
||||||
// Populate stats.
|
// Populate stats.
|
||||||
|
@ -339,7 +339,7 @@ func (u *utils) decrementFollowersCount(
|
||||||
account *gtsmodel.Account,
|
account *gtsmodel.Account,
|
||||||
) error {
|
) error {
|
||||||
// Lock on this account since we're changing stats.
|
// Lock on this account since we're changing stats.
|
||||||
unlock := u.state.AccountLocks.Lock(account.URI)
|
unlock := u.state.ProcessingLocks.Lock(account.URI)
|
||||||
defer unlock()
|
defer unlock()
|
||||||
|
|
||||||
// Populate stats.
|
// Populate stats.
|
||||||
|
@ -373,7 +373,7 @@ func (u *utils) incrementFollowingCount(
|
||||||
account *gtsmodel.Account,
|
account *gtsmodel.Account,
|
||||||
) error {
|
) error {
|
||||||
// Lock on this account since we're changing stats.
|
// Lock on this account since we're changing stats.
|
||||||
unlock := u.state.AccountLocks.Lock(account.URI)
|
unlock := u.state.ProcessingLocks.Lock(account.URI)
|
||||||
defer unlock()
|
defer unlock()
|
||||||
|
|
||||||
// Populate stats.
|
// Populate stats.
|
||||||
|
@ -402,7 +402,7 @@ func (u *utils) decrementFollowingCount(
|
||||||
account *gtsmodel.Account,
|
account *gtsmodel.Account,
|
||||||
) error {
|
) error {
|
||||||
// Lock on this account since we're changing stats.
|
// Lock on this account since we're changing stats.
|
||||||
unlock := u.state.AccountLocks.Lock(account.URI)
|
unlock := u.state.ProcessingLocks.Lock(account.URI)
|
||||||
defer unlock()
|
defer unlock()
|
||||||
|
|
||||||
// Populate stats.
|
// Populate stats.
|
||||||
|
@ -436,7 +436,7 @@ func (u *utils) incrementFollowRequestsCount(
|
||||||
account *gtsmodel.Account,
|
account *gtsmodel.Account,
|
||||||
) error {
|
) error {
|
||||||
// Lock on this account since we're changing stats.
|
// Lock on this account since we're changing stats.
|
||||||
unlock := u.state.AccountLocks.Lock(account.URI)
|
unlock := u.state.ProcessingLocks.Lock(account.URI)
|
||||||
defer unlock()
|
defer unlock()
|
||||||
|
|
||||||
// Populate stats.
|
// Populate stats.
|
||||||
|
@ -465,7 +465,7 @@ func (u *utils) decrementFollowRequestsCount(
|
||||||
account *gtsmodel.Account,
|
account *gtsmodel.Account,
|
||||||
) error {
|
) error {
|
||||||
// Lock on this account since we're changing stats.
|
// Lock on this account since we're changing stats.
|
||||||
unlock := u.state.AccountLocks.Lock(account.URI)
|
unlock := u.state.ProcessingLocks.Lock(account.URI)
|
||||||
defer unlock()
|
defer unlock()
|
||||||
|
|
||||||
// Populate stats.
|
// Populate stats.
|
||||||
|
|
|
@ -55,12 +55,12 @@ func New(
|
||||||
|
|
||||||
// Init surface logic
|
// Init surface logic
|
||||||
// wrapper struct.
|
// wrapper struct.
|
||||||
surface := &surface{
|
surface := &Surface{
|
||||||
state: state,
|
State: state,
|
||||||
converter: converter,
|
Converter: converter,
|
||||||
stream: stream,
|
Stream: stream,
|
||||||
filter: filter,
|
Filter: filter,
|
||||||
emailSender: emailSender,
|
EmailSender: emailSender,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init shared util funcs.
|
// Init shared util funcs.
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
|
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/cleaner"
|
"github.com/superseriousbusiness/gotosocial/internal/cleaner"
|
||||||
|
"github.com/superseriousbusiness/gotosocial/internal/email"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/filter/visibility"
|
"github.com/superseriousbusiness/gotosocial/internal/filter/visibility"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||||
|
@ -68,6 +69,7 @@ type TestStructs struct {
|
||||||
Processor *processing.Processor
|
Processor *processing.Processor
|
||||||
HTTPClient *testrig.MockHTTPClient
|
HTTPClient *testrig.MockHTTPClient
|
||||||
TypeConverter *typeutils.Converter
|
TypeConverter *typeutils.Converter
|
||||||
|
EmailSender email.Sender
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *WorkersTestSuite) SetupSuite() {
|
func (suite *WorkersTestSuite) SetupSuite() {
|
||||||
|
@ -168,6 +170,7 @@ func (suite *WorkersTestSuite) SetupTestStructs() *TestStructs {
|
||||||
Processor: processor,
|
Processor: processor,
|
||||||
HTTPClient: httpClient,
|
HTTPClient: httpClient,
|
||||||
TypeConverter: typeconverter,
|
TypeConverter: typeconverter,
|
||||||
|
EmailSender: emailSender,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -42,20 +42,21 @@ type State struct {
|
||||||
// DB provides access to the database.
|
// DB provides access to the database.
|
||||||
DB db.DB
|
DB db.DB
|
||||||
|
|
||||||
// FedLocks provides access to this state's
|
// FedLocks provides access to this state's mutex
|
||||||
// mutex map of per URI federation locks.
|
// map of per URI federation locks, intended for
|
||||||
|
// use in internal/federation functions.
|
||||||
//
|
//
|
||||||
// Used during account and status dereferencing,
|
// Used during account and status dereferencing,
|
||||||
// message processing in the FromFediAPI worker
|
// and by the go-fed/activity library.
|
||||||
// functions, and by the go-fed/activity library.
|
|
||||||
FedLocks mutexes.MutexMap
|
FedLocks mutexes.MutexMap
|
||||||
|
|
||||||
// AccountLocks provides access to this state's
|
// ProcessingLocks provides access to this state's
|
||||||
// mutex map of per URI locks, intended for use
|
// mutex map of per URI locks, intended for use
|
||||||
|
// in internal/processing functions, for example
|
||||||
// when updating accounts, migrating, approving
|
// when updating accounts, migrating, approving
|
||||||
// or rejecting an account, changing stats,
|
// or rejecting an account, changing stats or
|
||||||
// pinned statuses, etc.
|
// pinned statuses, creating notifs, etc.
|
||||||
AccountLocks mutexes.MutexMap
|
ProcessingLocks mutexes.MutexMap
|
||||||
|
|
||||||
// Storage provides access to the storage driver.
|
// Storage provides access to the storage driver.
|
||||||
Storage *storage.Driver
|
Storage *storage.Driver
|
||||||
|
|
Loading…
Reference in a new issue