Implement push subscription API

This commit is contained in:
Vyr Cossont 2024-11-30 20:13:06 -08:00
commit 8b9a228ea2
26 changed files with 2084 additions and 101 deletions

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"
@ -89,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
@ -147,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
}
@ -223,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)

View file

@ -0,0 +1,78 @@
// 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 {
return nil, gtserror.NewErrorInternalError(
gtserror.Newf("couldn't delete Web Push subscription for token ID %s: %w", tokenID, 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,
NotifyFollow: &request.Data.Alerts.Follow,
NotifyFollowRequest: &request.Data.Alerts.FollowRequest,
NotifyFavourite: &request.Data.Alerts.Favourite,
NotifyMention: &request.Data.Alerts.Mention,
NotifyReblog: &request.Data.Alerts.Reblog,
NotifyPoll: &request.Data.Alerts.Poll,
NotifyStatus: &request.Data.Alerts.Status,
NotifyUpdate: &request.Data.Alerts.Update,
NotifyAdminSignup: &request.Data.Alerts.AdminSignup,
NotifyAdminReport: &request.Data.Alerts.AdminReport,
NotifyPendingFavourite: &request.Data.Alerts.PendingFavourite,
NotifyPendingReply: &request.Data.Alerts.PendingReply,
NotifyPendingReblog: &request.Data.Alerts.PendingReblog,
}
if err := p.state.DB.PutWebPushSubscription(ctx, subscription); err != nil {
return nil, gtserror.NewErrorInternalError(
gtserror.Newf("couldn't create Web Push subscription for token ID %s: %w", tokenID, err),
)
}
return p.apiSubscription(ctx, subscription)
}

View file

@ -0,0 +1,40 @@
// 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 {
return gtserror.NewErrorInternalError(
gtserror.Newf("couldn't delete Web Push subscription for token ID %s: %w", tokenID, 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) {
return nil, gtserror.NewErrorInternalError(
gtserror.Newf("couldn't get Web Push subscription for token ID %s: %w", tokenID, err),
)
}
if subscription == nil {
return nil, gtserror.NewErrorNotFound(errors.New("no Web Push subscription exists for this access token"))
}
return p.apiSubscription(ctx, subscription)
}

View file

@ -0,0 +1,66 @@
// 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 {
return "", gtserror.NewErrorInternalError(
gtserror.Newf("couldn't find token ID for access token: %w", 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 {
return nil, gtserror.NewErrorInternalError(
gtserror.Newf("error converting Web Push subscription %s to API representation: %w", subscription.ID, err),
)
}
return apiSubscription, nil
}

View file

@ -0,0 +1,88 @@
// 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) {
return nil, gtserror.NewErrorInternalError(
gtserror.Newf("couldn't get Web Push subscription for token ID %s: %w", tokenID, err),
)
}
if subscription == nil {
return nil, gtserror.NewErrorNotFound(errors.New("no Web Push subscription exists for this access token"))
}
// Update it.
subscription.NotifyFollow = &request.Data.Alerts.Follow
subscription.NotifyFollowRequest = &request.Data.Alerts.FollowRequest
subscription.NotifyFavourite = &request.Data.Alerts.Favourite
subscription.NotifyMention = &request.Data.Alerts.Mention
subscription.NotifyReblog = &request.Data.Alerts.Reblog
subscription.NotifyPoll = &request.Data.Alerts.Poll
subscription.NotifyStatus = &request.Data.Alerts.Status
subscription.NotifyUpdate = &request.Data.Alerts.Update
subscription.NotifyAdminSignup = &request.Data.Alerts.AdminSignup
subscription.NotifyAdminReport = &request.Data.Alerts.AdminReport
subscription.NotifyPendingFavourite = &request.Data.Alerts.PendingFavourite
subscription.NotifyPendingReply = &request.Data.Alerts.PendingReply
subscription.NotifyPendingReblog = &request.Data.Alerts.PendingReblog
if err = p.state.DB.UpdateWebPushSubscription(
ctx,
subscription,
"notify_follow",
"notify_follow_request",
"notify_favourite",
"notify_mention",
"notify_reblog",
"notify_poll",
"notify_status",
"notify_update",
"notify_admin_signup",
"notify_admin_report",
"notify_pending_favourite",
"notify_pending_reply",
"notify_pending_reblog",
); err != nil {
return nil, gtserror.NewErrorInternalError(
gtserror.Newf("couldn't update Web Push subscription for token ID %s: %w", tokenID, err),
)
}
return p.apiSubscription(ctx, subscription)
}