Merge branch 'main' into xvello/import-mutes

This commit is contained in:
Xavier Vello 2025-01-27 17:29:59 +01:00 committed by GitHub
commit f30ab4bae4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
328 changed files with 31571 additions and 115701 deletions

View file

@ -96,7 +96,7 @@ func (p *Processor) Delete(
}
// deleteUserAndTokensForAccount deletes the gtsmodel.User and
// any OAuth tokens and applications for the given account.
// any OAuth tokens, applications, and Web Push subscriptions for the given account.
//
// Callers to this function should already have checked that
// this is a local account, or else it won't have a user associated
@ -129,6 +129,10 @@ func (p *Processor) deleteUserAndTokensForAccount(ctx context.Context, account *
}
}
if err := p.state.DB.DeleteWebPushSubscriptionsByAccountID(ctx, account.ID); err != nil {
return gtserror.Newf("db error deleting Web Push subscriptions: %w", err)
}
columns, err := stubbifyUser(user)
if err != nil {
return gtserror.Newf("error stubbifying user: %w", err)

View file

@ -119,6 +119,7 @@ func (suite *AdminStandardTestSuite) SetupTest() {
suite.mediaManager,
&suite.state,
suite.emailSender,
testrig.NewNoopWebPushSender(),
visibility.NewFilter(&suite.state),
interaction.NewFilter(&suite.state),
)

View file

@ -23,7 +23,6 @@ import (
"fmt"
"net/url"
"github.com/superseriousbusiness/activity/streams/vocab"
"github.com/superseriousbusiness/gotosocial/internal/ap"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
@ -72,7 +71,7 @@ func (p *Processor) UserGet(ctx context.Context, requestedUsername string, reque
}
// Auth passed, generate the proper AP representation.
person, err := p.converter.AccountToAS(ctx, receiver)
accountable, err := p.converter.AccountToAS(ctx, receiver)
if err != nil {
err := gtserror.Newf("error converting account: %w", err)
return nil, gtserror.NewErrorInternalError(err)
@ -91,7 +90,7 @@ func (p *Processor) UserGet(ctx context.Context, requestedUsername string, reque
// Instead, we end up in an 'I'll show you mine if you show me
// yours' situation, where we sort of agree to reveal each
// other's profiles at the same time.
return data(person)
return data(accountable)
}
// Get requester from auth.
@ -107,13 +106,13 @@ func (p *Processor) UserGet(ctx context.Context, requestedUsername string, reque
return nil, gtserror.NewErrorForbidden(errors.New(text))
}
return data(person)
return data(accountable)
}
func data(requestedPerson vocab.ActivityStreamsPerson) (interface{}, gtserror.WithCode) {
data, err := ap.Serialize(requestedPerson)
func data(accountable ap.Accountable) (interface{}, gtserror.WithCode) {
data, err := ap.Serialize(accountable)
if err != nil {
err := gtserror.Newf("error serializing person: %w", err)
err := gtserror.Newf("error serializing accountable: %w", err)
return nil, gtserror.NewErrorInternalError(err)
}

View file

@ -39,6 +39,7 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/processing/markers"
"github.com/superseriousbusiness/gotosocial/internal/processing/media"
"github.com/superseriousbusiness/gotosocial/internal/processing/polls"
"github.com/superseriousbusiness/gotosocial/internal/processing/push"
"github.com/superseriousbusiness/gotosocial/internal/processing/report"
"github.com/superseriousbusiness/gotosocial/internal/processing/search"
"github.com/superseriousbusiness/gotosocial/internal/processing/status"
@ -51,6 +52,7 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/subscriptions"
"github.com/superseriousbusiness/gotosocial/internal/text"
"github.com/superseriousbusiness/gotosocial/internal/typeutils"
"github.com/superseriousbusiness/gotosocial/internal/webpush"
)
// Processor groups together processing functions and
@ -88,6 +90,7 @@ type Processor struct {
markers markers.Processor
media media.Processor
polls polls.Processor
push push.Processor
report report.Processor
search search.Processor
status status.Processor
@ -146,6 +149,10 @@ func (p *Processor) Polls() *polls.Processor {
return &p.polls
}
func (p *Processor) Push() *push.Processor {
return &p.push
}
func (p *Processor) Report() *report.Processor {
return &p.report
}
@ -188,6 +195,7 @@ func NewProcessor(
mediaManager *mm.Manager,
state *state.State,
emailSender email.Sender,
webPushSender webpush.Sender,
visFilter *visibility.Filter,
intFilter *interaction.Filter,
) *Processor {
@ -221,6 +229,7 @@ func NewProcessor(
processor.list = list.New(state, converter)
processor.markers = markers.New(state, converter)
processor.polls = polls.New(&common, state, converter)
processor.push = push.New(state, converter)
processor.report = report.New(state, converter)
processor.tags = tags.New(state, converter)
processor.timeline = timeline.New(state, converter, visFilter)
@ -241,6 +250,7 @@ func NewProcessor(
converter,
visFilter,
emailSender,
webPushSender,
&processor.account,
&processor.media,
&processor.stream,

View file

@ -135,6 +135,7 @@ func (suite *ProcessingStandardTestSuite) SetupTest() {
suite.mediaManager,
&suite.state,
suite.emailSender,
testrig.NewNoopWebPushSender(),
visibility.NewFilter(&suite.state),
interaction.NewFilter(&suite.state),
)

View file

@ -0,0 +1,65 @@
// 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 push
import (
"context"
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/id"
)
// CreateOrReplace creates a Web Push subscription for the given access token,
// or entirely replaces the previously existing subscription for that token.
func (p *Processor) CreateOrReplace(
ctx context.Context,
accountID string,
accessToken string,
request *apimodel.WebPushSubscriptionCreateRequest,
) (*apimodel.WebPushSubscription, gtserror.WithCode) {
tokenID, errWithCode := p.getTokenID(ctx, accessToken)
if errWithCode != nil {
return nil, errWithCode
}
// Clear any previous subscription.
if err := p.state.DB.DeleteWebPushSubscriptionByTokenID(ctx, tokenID); err != nil {
err := gtserror.Newf("couldn't delete Web Push subscription for token ID %s: %w", tokenID, err)
return nil, gtserror.NewErrorInternalError(err)
}
// Insert a new one.
subscription := &gtsmodel.WebPushSubscription{
ID: id.NewULID(),
AccountID: accountID,
TokenID: tokenID,
Endpoint: request.Subscription.Endpoint,
Auth: request.Subscription.Keys.Auth,
P256dh: request.Subscription.Keys.P256dh,
NotificationFlags: alertsToNotificationFlags(request.Data.Alerts),
}
if err := p.state.DB.PutWebPushSubscription(ctx, subscription); err != nil {
err := gtserror.Newf("couldn't create Web Push subscription for token ID %s: %w", tokenID, err)
return nil, gtserror.NewErrorInternalError(err)
}
return p.apiSubscription(ctx, subscription)
}

View file

@ -0,0 +1,39 @@
// 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 push
import (
"context"
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
)
// Delete deletes the Web Push subscription for the given access token, if there is one.
func (p *Processor) Delete(ctx context.Context, accessToken string) gtserror.WithCode {
tokenID, errWithCode := p.getTokenID(ctx, accessToken)
if errWithCode != nil {
return errWithCode
}
if err := p.state.DB.DeleteWebPushSubscriptionByTokenID(ctx, tokenID); err != nil {
err := gtserror.Newf("couldn't delete Web Push subscription for token ID %s: %w", tokenID, err)
return gtserror.NewErrorInternalError(err)
}
return nil
}

View file

@ -0,0 +1,47 @@
// 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 push
import (
"context"
"errors"
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
)
// Get returns the Web Push subscription for the given access token.
func (p *Processor) Get(ctx context.Context, accessToken string) (*apimodel.WebPushSubscription, gtserror.WithCode) {
tokenID, errWithCode := p.getTokenID(ctx, accessToken)
if errWithCode != nil {
return nil, errWithCode
}
subscription, err := p.state.DB.GetWebPushSubscriptionByTokenID(ctx, tokenID)
if err != nil && !errors.Is(err, db.ErrNoEntries) {
err := gtserror.Newf("couldn't get Web Push subscription for token ID %s: %w", tokenID, err)
return nil, gtserror.NewErrorInternalError(err)
}
if subscription == nil {
err := errors.New("no Web Push subscription exists for this access token")
return nil, gtserror.NewErrorNotFound(err)
}
return p.apiSubscription(ctx, subscription)
}

View file

@ -0,0 +1,85 @@
// 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 push
import (
"context"
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/state"
"github.com/superseriousbusiness/gotosocial/internal/typeutils"
)
type Processor struct {
state *state.State
converter *typeutils.Converter
}
func New(state *state.State, converter *typeutils.Converter) Processor {
return Processor{
state: state,
converter: converter,
}
}
// getTokenID returns the token ID for a given access token.
// Since all push API calls require authentication, this should always be available.
func (p *Processor) getTokenID(ctx context.Context, accessToken string) (string, gtserror.WithCode) {
token, err := p.state.DB.GetTokenByAccess(ctx, accessToken)
if err != nil {
err := gtserror.Newf("couldn't find token ID for access token: %w", err)
return "", gtserror.NewErrorInternalError(err)
}
return token.ID, nil
}
// apiSubscription is a shortcut to return the API version of the given Web Push subscription,
// or return an appropriate error if conversion fails.
func (p *Processor) apiSubscription(ctx context.Context, subscription *gtsmodel.WebPushSubscription) (*apimodel.WebPushSubscription, gtserror.WithCode) {
apiSubscription, err := p.converter.WebPushSubscriptionToAPIWebPushSubscription(ctx, subscription)
if err != nil {
err := gtserror.Newf("error converting Web Push subscription %s to API representation: %w", subscription.ID, err)
return nil, gtserror.NewErrorInternalError(err)
}
return apiSubscription, nil
}
// alertsToNotificationFlags turns the alerts section of a push subscription API request into a packed bitfield.
func alertsToNotificationFlags(alerts *apimodel.WebPushSubscriptionAlerts) gtsmodel.WebPushSubscriptionNotificationFlags {
var n gtsmodel.WebPushSubscriptionNotificationFlags
n.Set(gtsmodel.NotificationFollow, alerts.Follow)
n.Set(gtsmodel.NotificationFollowRequest, alerts.FollowRequest)
n.Set(gtsmodel.NotificationFavourite, alerts.Favourite)
n.Set(gtsmodel.NotificationMention, alerts.Mention)
n.Set(gtsmodel.NotificationReblog, alerts.Reblog)
n.Set(gtsmodel.NotificationPoll, alerts.Poll)
n.Set(gtsmodel.NotificationStatus, alerts.Status)
n.Set(gtsmodel.NotificationUpdate, alerts.Update)
n.Set(gtsmodel.NotificationAdminSignup, alerts.AdminSignup)
n.Set(gtsmodel.NotificationAdminReport, alerts.AdminReport)
n.Set(gtsmodel.NotificationPendingFave, alerts.PendingFavourite)
n.Set(gtsmodel.NotificationPendingReply, alerts.PendingReply)
n.Set(gtsmodel.NotificationPendingReblog, alerts.PendingReblog)
return n
}

View file

@ -0,0 +1,63 @@
// 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 push
import (
"context"
"errors"
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
)
// Update updates the Web Push subscription for the given access token.
func (p *Processor) Update(
ctx context.Context,
accessToken string,
request *apimodel.WebPushSubscriptionUpdateRequest,
) (*apimodel.WebPushSubscription, gtserror.WithCode) {
tokenID, errWithCode := p.getTokenID(ctx, accessToken)
if errWithCode != nil {
return nil, errWithCode
}
// Get existing subscription.
subscription, err := p.state.DB.GetWebPushSubscriptionByTokenID(ctx, tokenID)
if err != nil && !errors.Is(err, db.ErrNoEntries) {
err := gtserror.Newf("couldn't get Web Push subscription for token ID %s: %w", tokenID, err)
return nil, gtserror.NewErrorInternalError(err)
}
if subscription == nil {
err := errors.New("no Web Push subscription exists for this access token")
return nil, gtserror.NewErrorNotFound(err)
}
// Update it.
subscription.NotificationFlags = alertsToNotificationFlags(request.Data.Alerts)
if err = p.state.DB.UpdateWebPushSubscription(
ctx,
subscription,
"notification_flags",
); err != nil {
err := gtserror.Newf("couldn't update Web Push subscription for token ID %s: %w", tokenID, err)
return nil, gtserror.NewErrorInternalError(err)
}
return p.apiSubscription(ctx, subscription)
}

View file

@ -184,7 +184,7 @@ func (p *Processor) notifVisible(
// If this is a new local account sign-up,
// skip normal visibility checking because
// origin account won't be confirmed yet.
if n.NotificationType == gtsmodel.NotificationSignup {
if n.NotificationType == gtsmodel.NotificationAdminSignup {
return true, nil
}

View file

@ -21,7 +21,6 @@ import (
"context"
"net/url"
"github.com/superseriousbusiness/activity/pub"
"github.com/superseriousbusiness/activity/streams"
"github.com/superseriousbusiness/activity/streams/vocab"
"github.com/superseriousbusiness/gotosocial/internal/ap"
@ -93,11 +92,6 @@ func (f *federate) DeleteAccount(ctx context.Context, account *gtsmodel.Account)
return err
}
publicIRI, err := parseURI(pub.PublicActivityPubIRI)
if err != nil {
return err
}
// Create a new delete.
// todo: tc.AccountToASDelete
delete := streams.NewActivityStreamsDelete()
@ -121,7 +115,7 @@ func (f *federate) DeleteAccount(ctx context.Context, account *gtsmodel.Account)
// Address the delete CC public.
deleteCC := streams.NewActivityStreamsCcProperty()
deleteCC.AppendIRI(publicIRI)
deleteCC.AppendIRI(ap.PublicURI())
delete.SetActivityStreamsCc(deleteCC)
// Send the Delete via the Actor's outbox.
@ -877,14 +871,14 @@ func (f *federate) UpdateAccount(ctx context.Context, account *gtsmodel.Account)
return err
}
// Convert account to ActivityStreams Person.
person, err := f.converter.AccountToAS(ctx, account)
// Convert account to Accountable.
accountable, err := f.converter.AccountToAS(ctx, account)
if err != nil {
return gtserror.Newf("error converting account to Person: %w", err)
}
// Use ActivityStreams Person as Object of Update.
update, err := f.converter.WrapPersonInUpdate(person, account)
// Use Accountable as Object of Update.
update, err := f.converter.WrapAccountableInUpdate(accountable)
if err != nil {
return gtserror.Newf("error wrapping Person in Update: %w", err)
}
@ -1089,11 +1083,6 @@ func (f *federate) MoveAccount(ctx context.Context, account *gtsmodel.Account) e
return err
}
publicIRI, err := parseURI(pub.PublicActivityPubIRI)
if err != nil {
return err
}
// Create a new move.
move := streams.NewActivityStreamsMove()
@ -1115,7 +1104,7 @@ func (f *federate) MoveAccount(ctx context.Context, account *gtsmodel.Account) e
ap.AppendTo(move, followersIRI)
// Address the move CC public.
ap.AppendCc(move, publicIRI)
ap.AppendCc(move, ap.PublicURI())
// Send the Move via the Actor's outbox.
if _, err := f.FederatingActor().Send(

View file

@ -179,6 +179,28 @@ func (suite *FromClientAPITestSuite) checkStreamed(
}
}
// checkWebPushed asserts that the target account got a single Web Push notification with a given type.
func (suite *FromClientAPITestSuite) checkWebPushed(
sender *testrig.WebPushMockSender,
accountID string,
notificationType gtsmodel.NotificationType,
) {
pushedNotifications := sender.Sent[accountID]
if suite.Len(pushedNotifications, 1) {
pushedNotification := pushedNotifications[0]
suite.Equal(notificationType, pushedNotification.NotificationType)
}
}
// checkNotWebPushed asserts that the target account got no Web Push notifications.
func (suite *FromClientAPITestSuite) checkNotWebPushed(
sender *testrig.WebPushMockSender,
accountID string,
) {
pushedNotifications := sender.Sent[accountID]
suite.Len(pushedNotifications, 0)
}
func (suite *FromClientAPITestSuite) statusJSON(
ctx context.Context,
typeConverter *typeutils.Converter,
@ -341,6 +363,9 @@ func (suite *FromClientAPITestSuite) TestProcessCreateStatusWithNotification() {
string(notifJSON),
stream.EventTypeNotification,
)
// Check for a Web Push status notification.
suite.checkWebPushed(testStructs.WebPushSender, receivingAccount.ID, gtsmodel.NotificationStatus)
}
func (suite *FromClientAPITestSuite) TestProcessCreateStatusReply() {
@ -409,6 +434,9 @@ func (suite *FromClientAPITestSuite) TestProcessCreateStatusReply() {
statusJSON,
stream.EventTypeUpdate,
)
// Check for absence of Web Push notifications.
suite.checkNotWebPushed(testStructs.WebPushSender, receivingAccount.ID)
}
func (suite *FromClientAPITestSuite) TestProcessCreateStatusReplyMuted() {
@ -470,6 +498,9 @@ func (suite *FromClientAPITestSuite) TestProcessCreateStatusReplyMuted() {
suite.ErrorIs(err, db.ErrNoEntries)
suite.Nil(notif)
// Check for absence of Web Push notifications.
suite.checkNotWebPushed(testStructs.WebPushSender, receivingAccount.ID)
}
func (suite *FromClientAPITestSuite) TestProcessCreateStatusBoostMuted() {
@ -531,6 +562,9 @@ func (suite *FromClientAPITestSuite) TestProcessCreateStatusBoostMuted() {
suite.ErrorIs(err, db.ErrNoEntries)
suite.Nil(notif)
// Check for absence of Web Push notifications.
suite.checkNotWebPushed(testStructs.WebPushSender, receivingAccount.ID)
}
func (suite *FromClientAPITestSuite) TestProcessCreateStatusListRepliesPolicyListOnlyOK() {
@ -607,6 +641,9 @@ func (suite *FromClientAPITestSuite) TestProcessCreateStatusListRepliesPolicyLis
statusJSON,
stream.EventTypeUpdate,
)
// Check for absence of Web Push notifications.
suite.checkNotWebPushed(testStructs.WebPushSender, receivingAccount.ID)
}
func (suite *FromClientAPITestSuite) TestProcessCreateStatusListRepliesPolicyListOnlyNo() {
@ -689,6 +726,9 @@ func (suite *FromClientAPITestSuite) TestProcessCreateStatusListRepliesPolicyLis
"",
"",
)
// Check for absence of Web Push notifications.
suite.checkNotWebPushed(testStructs.WebPushSender, receivingAccount.ID)
}
func (suite *FromClientAPITestSuite) TestProcessCreateStatusReplyListRepliesPolicyNone() {
@ -765,6 +805,9 @@ func (suite *FromClientAPITestSuite) TestProcessCreateStatusReplyListRepliesPoli
"",
"",
)
// Check for absence of Web Push notifications.
suite.checkNotWebPushed(testStructs.WebPushSender, receivingAccount.ID)
}
func (suite *FromClientAPITestSuite) TestProcessCreateStatusBoost() {
@ -829,6 +872,9 @@ func (suite *FromClientAPITestSuite) TestProcessCreateStatusBoost() {
statusJSON,
stream.EventTypeUpdate,
)
// Check for absence of Web Push notifications.
suite.checkNotWebPushed(testStructs.WebPushSender, receivingAccount.ID)
}
func (suite *FromClientAPITestSuite) TestProcessCreateStatusBoostNoReblogs() {
@ -981,6 +1027,9 @@ func (suite *FromClientAPITestSuite) TestProcessCreateStatusWhichBeginsConversat
conversationJSON,
stream.EventTypeConversation,
)
// Check for a Web Push mention notification.
suite.checkWebPushed(testStructs.WebPushSender, receivingAccount.ID, gtsmodel.NotificationMention)
}
// A public message to a local user should not result in a conversation notification.
@ -1050,6 +1099,9 @@ func (suite *FromClientAPITestSuite) TestProcessCreateStatusWhichShouldNotCreate
"",
"",
)
// Check for a Web Push mention notification.
suite.checkWebPushed(testStructs.WebPushSender, receivingAccount.ID, gtsmodel.NotificationMention)
}
// A public status with a hashtag followed by a local user who does not otherwise follow the author
@ -1123,6 +1175,9 @@ func (suite *FromClientAPITestSuite) TestProcessCreateStatusWithFollowedHashtag(
"",
stream.EventTypeUpdate,
)
// Check for absence of Web Push notifications.
suite.checkNotWebPushed(testStructs.WebPushSender, receivingAccount.ID)
}
// A public status with a hashtag followed by a local user who does not otherwise follow the author
@ -1204,6 +1259,9 @@ func (suite *FromClientAPITestSuite) TestProcessCreateStatusWithFollowedHashtagA
"",
"",
)
// Check for absence of Web Push notifications.
suite.checkNotWebPushed(testStructs.WebPushSender, receivingAccount.ID)
}
// A boost of a public status with a hashtag followed by a local user
@ -1306,6 +1364,9 @@ func (suite *FromClientAPITestSuite) TestProcessCreateBoostWithFollowedHashtag()
"",
stream.EventTypeUpdate,
)
// Check for absence of Web Push notifications.
suite.checkNotWebPushed(testStructs.WebPushSender, receivingAccount.ID)
}
// A boost of a public status with a hashtag followed by a local user
@ -1416,6 +1477,9 @@ func (suite *FromClientAPITestSuite) TestProcessCreateBoostWithFollowedHashtagAn
"",
"",
)
// Check for absence of Web Push notifications.
suite.checkNotWebPushed(testStructs.WebPushSender, receivingAccount.ID)
}
// A boost of a public status with a hashtag followed by a local user
@ -1526,6 +1590,9 @@ func (suite *FromClientAPITestSuite) TestProcessCreateBoostWithFollowedHashtagAn
"",
"",
)
// Check for absence of Web Push notifications.
suite.checkNotWebPushed(testStructs.WebPushSender, receivingAccount.ID)
}
// A public status with a hashtag followed by a local user who follows the author and has them on an exclusive list
@ -1598,6 +1665,9 @@ func (suite *FromClientAPITestSuite) TestProcessCreateStatusWithAuthorOnExclusiv
"",
"",
)
// Check for absence of Web Push notifications.
suite.checkNotWebPushed(testStructs.WebPushSender, receivingAccount.ID)
}
// A public status with a hashtag followed by a local user who follows the author and has them on an exclusive list
@ -1712,6 +1782,9 @@ func (suite *FromClientAPITestSuite) TestProcessCreateStatusWithAuthorOnExclusiv
"",
"",
)
// Check for absence of Web Push notifications.
suite.checkNotWebPushed(testStructs.WebPushSender, receivingAccount.ID)
}
// A public status with a hashtag followed by a local user who follows the author and has them on an exclusive list
@ -1837,6 +1910,9 @@ func (suite *FromClientAPITestSuite) TestProcessCreateStatusWithAuthorOnExclusiv
"",
"",
)
// Check for a Web Push status notification.
suite.checkWebPushed(testStructs.WebPushSender, receivingAccount.ID, gtsmodel.NotificationStatus)
}
// Updating a public status with a hashtag followed by a local user who does not otherwise follow the author
@ -1910,6 +1986,9 @@ func (suite *FromClientAPITestSuite) TestProcessUpdateStatusWithFollowedHashtag(
"",
stream.EventTypeStatusUpdate,
)
// Check for absence of Web Push notifications.
suite.checkNotWebPushed(testStructs.WebPushSender, receivingAccount.ID)
}
func (suite *FromClientAPITestSuite) TestProcessStatusDelete() {
@ -1963,6 +2042,9 @@ func (suite *FromClientAPITestSuite) TestProcessStatusDelete() {
stream.EventTypeDelete,
)
// Check for absence of Web Push notifications.
suite.checkNotWebPushed(testStructs.WebPushSender, receivingAccount.ID)
// Boost should no longer be in the database.
if !testrig.WaitFor(func() bool {
_, err := testStructs.State.DB.GetStatusByID(ctx, boostOfDeletedStatus.ID)

View file

@ -189,6 +189,14 @@ func (p *Processor) ProcessFromFediAPI(ctx context.Context, fMsg *messages.FromF
if fMsg.APObjectType == ap.ActorPerson {
return p.fediAPI.MoveAccount(ctx, fMsg)
}
// UNDO SOMETHING
case ap.ActivityUndo:
// UNDO ANNOUNCE
if fMsg.APObjectType == ap.ActivityAnnounce {
return p.fediAPI.UndoAnnounce(ctx, fMsg)
}
}
return gtserror.Newf("unhandled: %s %s", fMsg.APActivityType, fMsg.APObjectType)
@ -1159,3 +1167,34 @@ func (p *fediAPI) RejectAnnounce(ctx context.Context, fMsg *messages.FromFediAPI
return nil
}
func (p *fediAPI) UndoAnnounce(
ctx context.Context,
fMsg *messages.FromFediAPI,
) error {
boost, ok := fMsg.GTSModel.(*gtsmodel.Status)
if !ok {
return gtserror.Newf("%T not parseable as *gtsmodel.Status", fMsg.GTSModel)
}
// Delete the boost wrapper itself.
if err := p.state.DB.DeleteStatusByID(ctx, boost.ID); err != nil {
return gtserror.Newf("db error deleting boost: %w", err)
}
// Update statuses count for the requesting account.
if err := p.utils.decrementStatusesCount(ctx, fMsg.Requesting, boost); err != nil {
log.Errorf(ctx, "error updating account stats: %v", err)
}
// Remove the boost wrapper from all timelines.
if err := p.surface.deleteStatusFromTimelines(ctx, boost.ID); err != nil {
log.Errorf(ctx, "error removing timelined boost: %v", err)
}
// Interaction counts changed on the boosted status;
// uncache the prepared version from all timelines.
p.surface.invalidateStatusFromTimelines(ctx, boost.BoostOfID)
return nil
}

View file

@ -20,6 +20,7 @@ package workers_test
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"testing"
@ -29,6 +30,7 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/ap"
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtscontext"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/messages"
"github.com/superseriousbusiness/gotosocial/internal/stream"
@ -240,7 +242,7 @@ func (suite *FromFediAPITestSuite) TestProcessFave() {
notif := &gtsmodel.Notification{}
err = testStructs.State.DB.GetWhere(context.Background(), where, notif)
suite.NoError(err)
suite.Equal(gtsmodel.NotificationFave, notif.NotificationType)
suite.Equal(gtsmodel.NotificationFavourite, notif.NotificationType)
suite.Equal(fave.TargetAccountID, notif.TargetAccountID)
suite.Equal(fave.AccountID, notif.OriginAccountID)
suite.Equal(fave.StatusID, notif.StatusID)
@ -313,7 +315,7 @@ func (suite *FromFediAPITestSuite) TestProcessFaveWithDifferentReceivingAccount(
notif := &gtsmodel.Notification{}
err = testStructs.State.DB.GetWhere(context.Background(), where, notif)
suite.NoError(err)
suite.Equal(gtsmodel.NotificationFave, notif.NotificationType)
suite.Equal(gtsmodel.NotificationFavourite, notif.NotificationType)
suite.Equal(fave.TargetAccountID, notif.TargetAccountID)
suite.Equal(fave.AccountID, notif.OriginAccountID)
suite.Equal(fave.StatusID, notif.StatusID)
@ -679,6 +681,60 @@ func (suite *FromFediAPITestSuite) TestMoveAccount() {
suite.WithinDuration(time.Now(), move.SucceededAt, 1*time.Minute)
}
func (suite *FromFediAPITestSuite) TestUndoAnnounce() {
var (
ctx = context.Background()
testStructs = testrig.SetupTestStructs(rMediaPath, rTemplatePath)
requestingAcct = suite.testAccounts["remote_account_1"]
receivingAcct = suite.testAccounts["local_account_1"]
boostedStatus = suite.testStatuses["admin_account_status_1"]
)
defer testrig.TearDownTestStructs(testStructs)
// Have remote_account_1 boost admin_account.
boost, err := testStructs.TypeConverter.StatusToBoost(
ctx,
boostedStatus,
requestingAcct,
"",
)
if err != nil {
suite.FailNow(err.Error())
}
// Set the boost URI + URL to
// fossbros-anonymous.io.
boost.URI = "https://fossbros-anonymous.io/users/foss_satan/" + boost.ID
boost.URL = boost.URI
// Store the boost.
if err := testStructs.State.DB.PutStatus(ctx, boost); err != nil {
suite.FailNow(err.Error())
}
// Process the Undo.
err = testStructs.Processor.Workers().ProcessFromFediAPI(ctx, &messages.FromFediAPI{
APObjectType: ap.ActivityAnnounce,
APActivityType: ap.ActivityUndo,
GTSModel: boost,
Receiving: receivingAcct,
Requesting: requestingAcct,
})
suite.NoError(err)
// Wait for side effects to trigger:
// the boost should be deleted.
if !testrig.WaitFor(func() bool {
_, err := testStructs.State.DB.GetStatusByID(
gtscontext.SetBarebones(ctx),
boost.ID,
)
return errors.Is(err, db.ErrNoEntries)
}) {
suite.FailNow("timed out waiting for boost to be removed")
}
}
func TestFromFederatorTestSuite(t *testing.T) {
suite.Run(t, &FromFediAPITestSuite{})
}

View file

@ -24,6 +24,7 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/processing/stream"
"github.com/superseriousbusiness/gotosocial/internal/state"
"github.com/superseriousbusiness/gotosocial/internal/typeutils"
"github.com/superseriousbusiness/gotosocial/internal/webpush"
)
// Surface wraps functions for 'surfacing' the result
@ -38,5 +39,6 @@ type Surface struct {
Stream *stream.Processor
VisFilter *visibility.Filter
EmailSender email.Sender
WebPushSender webpush.Sender
Conversations *conversations.Processor
}

View file

@ -250,7 +250,7 @@ func (s *Surface) notifyFave(
// notify status author
// of fave by account.
if err := s.Notify(ctx,
gtsmodel.NotificationFave,
gtsmodel.NotificationFavourite,
fave.TargetAccount,
fave.Account,
fave.StatusID,
@ -521,7 +521,7 @@ func (s *Surface) notifySignup(ctx context.Context, newUser *gtsmodel.User) erro
var errs gtserror.MultiError
for _, mod := range modAccounts {
if err := s.Notify(ctx,
gtsmodel.NotificationSignup,
gtsmodel.NotificationAdminSignup,
mod,
newUser.Account,
"",
@ -647,5 +647,10 @@ func (s *Surface) Notify(
}
s.Stream.Notify(ctx, targetAccount, apiNotif)
// Send Web Push notification to the user.
if err = s.WebPushSender.Send(ctx, notif, filters, compiledMutes); err != nil {
return gtserror.Newf("error sending Web Push notifications: %w", err)
}
return nil
}

View file

@ -45,6 +45,7 @@ func (suite *SurfaceNotifyTestSuite) TestSpamNotifs() {
Stream: testStructs.Processor.Stream(),
VisFilter: visibility.NewFilter(testStructs.State),
EmailSender: testStructs.EmailSender,
WebPushSender: testStructs.WebPushSender,
Conversations: testStructs.Processor.Conversations(),
}

View file

@ -28,6 +28,7 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/processing/stream"
"github.com/superseriousbusiness/gotosocial/internal/state"
"github.com/superseriousbusiness/gotosocial/internal/typeutils"
"github.com/superseriousbusiness/gotosocial/internal/webpush"
"github.com/superseriousbusiness/gotosocial/internal/workers"
)
@ -44,6 +45,7 @@ func New(
converter *typeutils.Converter,
visFilter *visibility.Filter,
emailSender email.Sender,
webPushSender webpush.Sender,
account *account.Processor,
media *media.Processor,
stream *stream.Processor,
@ -65,6 +67,7 @@ func New(
Stream: stream,
VisFilter: visFilter,
EmailSender: emailSender,
WebPushSender: webPushSender,
Conversations: conversations,
}