Skip to content

Commit

Permalink
[feature] Implement Web Push notification policy (#3721)
Browse files Browse the repository at this point in the history
* Web Push: add policy column to subscriptions

* Web Push: add policy to API

* Web Push: test notification policy

* go-fmt unrelated file (how did this get thru?)
  • Loading branch information
VyrCossont authored Feb 3, 2025
1 parent 8b74cad commit 27844b7
Show file tree
Hide file tree
Showing 16 changed files with 340 additions and 35 deletions.
20 changes: 20 additions & 0 deletions docs/api/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9922,6 +9922,16 @@ paths:
in: formData
name: data[alerts][pending.reblog]
type: boolean
- default: all
description: Which accounts to receive push notifications from.
enum:
- all
- followed
- follower
- none
in: formData
name: data[policy]
type: string
produces:
- application/json
responses:
Expand Down Expand Up @@ -10019,6 +10029,16 @@ paths:
in: formData
name: data[alerts][pending.reblog]
type: boolean
- default: all
description: Which accounts to receive push notifications from.
enum:
- all
- followed
- follower
- none
in: formData
name: data[policy]
type: string
produces:
- application/json
responses:
Expand Down
11 changes: 11 additions & 0 deletions internal/api/client/push/pushsubscriptionpost.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,17 @@ import (
// type: boolean
// default: false
// description: Receive a push notification when a boost is pending?
// -
// name: data[policy]
// in: formData
// type: string
// enum:
// - all
// - followed
// - follower
// - none
// default: all
// description: Which accounts to receive push notifications from.
//
// security:
// - OAuth2 Bearer:
Expand Down
22 changes: 21 additions & 1 deletion internal/api/client/push/pushsubscriptionpost_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ func (suite *PushTestSuite) postSubscription(
p256dh *string,
alertsMention *bool,
alertsStatus *bool,
policy *string,
requestJson *string,
expectedHTTPStatus int,
) (*apimodel.WebPushSubscription, error) {
Expand Down Expand Up @@ -80,6 +81,9 @@ func (suite *PushTestSuite) postSubscription(
if alertsStatus != nil {
ctx.Request.Form["data[alerts][status]"] = []string{strconv.FormatBool(*alertsStatus)}
}
if policy != nil {
ctx.Request.Form["data[policy]"] = []string{*policy}
}
}

// trigger the handler
Expand Down Expand Up @@ -119,6 +123,7 @@ func (suite *PushTestSuite) TestPostSubscription() {
p256dh := "BMYVItYVOX+AHBdtA62Q0i6c+F7MV2Gia3aoDr8mvHkuPBNIOuTLDfmFcnBqoZcQk6BtLcIONbxhHpy2R+mYIUY="
alertsMention := true
alertsStatus := false
policy := "followed"
subscription, err := suite.postSubscription(
accountFixtureName,
tokenFixtureName,
Expand All @@ -127,6 +132,7 @@ func (suite *PushTestSuite) TestPostSubscription() {
&p256dh,
&alertsMention,
&alertsStatus,
&policy,
nil,
200,
)
Expand All @@ -138,6 +144,7 @@ func (suite *PushTestSuite) TestPostSubscription() {
suite.False(subscription.Alerts.Status)
// Omitted event types should default to off.
suite.False(subscription.Alerts.Favourite)
suite.Equal(apimodel.WebPushNotificationPolicyFollowed, subscription.Policy)
}
}

Expand All @@ -159,6 +166,7 @@ func (suite *PushTestSuite) TestPostSubscriptionMinimal() {
nil,
nil,
nil,
nil,
200,
)
if suite.NoError(err) {
Expand All @@ -169,6 +177,8 @@ func (suite *PushTestSuite) TestPostSubscriptionMinimal() {
suite.False(subscription.Alerts.Mention)
suite.False(subscription.Alerts.Status)
suite.False(subscription.Alerts.Favourite)
// Policy should default to all.
suite.Equal(apimodel.WebPushNotificationPolicyAll, subscription.Policy)
}
}

Expand All @@ -192,6 +202,7 @@ func (suite *PushTestSuite) TestPostInvalidSubscription() {
&alertsMention,
&alertsStatus,
nil,
nil,
422,
)
suite.NoError(err)
Expand All @@ -215,7 +226,8 @@ func (suite *PushTestSuite) TestPostSubscriptionJSON() {
"alerts": {
"mention": true,
"status": false
}
},
"policy": "followed"
}
}`
subscription, err := suite.postSubscription(
Expand All @@ -226,6 +238,7 @@ func (suite *PushTestSuite) TestPostSubscriptionJSON() {
nil,
nil,
nil,
nil,
&requestJson,
200,
)
Expand All @@ -237,6 +250,7 @@ func (suite *PushTestSuite) TestPostSubscriptionJSON() {
suite.False(subscription.Alerts.Status)
// Omitted event types should default to off.
suite.False(subscription.Alerts.Favourite)
suite.Equal(apimodel.WebPushNotificationPolicyFollowed, subscription.Policy)
}
}

Expand All @@ -263,6 +277,7 @@ func (suite *PushTestSuite) TestPostSubscriptionJSONMinimal() {
nil,
nil,
nil,
nil,
&requestJson,
200,
)
Expand All @@ -274,6 +289,8 @@ func (suite *PushTestSuite) TestPostSubscriptionJSONMinimal() {
suite.False(subscription.Alerts.Mention)
suite.False(subscription.Alerts.Status)
suite.False(subscription.Alerts.Favourite)
// Policy should default to all.
suite.Equal(apimodel.WebPushNotificationPolicyAll, subscription.Policy)
}
}

Expand Down Expand Up @@ -306,6 +323,7 @@ func (suite *PushTestSuite) TestPostInvalidSubscriptionJSON() {
nil,
nil,
nil,
nil,
&requestJson,
422,
)
Expand All @@ -323,6 +341,7 @@ func (suite *PushTestSuite) TestPostExistingSubscription() {
p256dh := "BMYVItYVOX+AHBdtA62Q0i6c+F7MV2Gia3aoDr8mvHkuPBNIOuTLDfmFcnBqoZcQk6BtLcIONbxhHpy2R+mYIUY="
alertsMention := true
alertsStatus := false
policy := "followed"
subscription, err := suite.postSubscription(
accountFixtureName,
tokenFixtureName,
Expand All @@ -331,6 +350,7 @@ func (suite *PushTestSuite) TestPostExistingSubscription() {
&p256dh,
&alertsMention,
&alertsStatus,
&policy,
nil,
200,
)
Expand Down
22 changes: 21 additions & 1 deletion internal/api/client/push/pushsubscriptionput.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
"github.com/superseriousbusiness/gotosocial/internal/oauth"
"github.com/superseriousbusiness/gotosocial/internal/util"
)

// PushSubscriptionPUTHandler swagger:operation PUT /api/v1/push/subscription pushSubscriptionPut
Expand Down Expand Up @@ -122,6 +123,17 @@ import (
// type: boolean
// default: false
// description: Receive a push notification when a boost is pending?
// -
// name: data[policy]
// in: formData
// type: string
// enum:
// - all
// - followed
// - follower
// - none
// default: all
// description: Which accounts to receive push notifications from.
//
// security:
// - OAuth2 Bearer:
Expand Down Expand Up @@ -181,7 +193,8 @@ func (m *Module) PushSubscriptionPUTHandler(c *gin.Context) {
apiutil.JSON(c, http.StatusOK, apiSubscription)
}

// validateNormalizeUpdate copies form fields to their canonical JSON equivalents.
// validateNormalizeUpdate copies form fields to their canonical JSON equivalents
// and sets defaults for fields that have them.
func validateNormalizeUpdate(request *apimodel.WebPushSubscriptionUpdateRequest) error {
if request.Data == nil {
request.Data = &apimodel.WebPushSubscriptionRequestData{}
Expand Down Expand Up @@ -228,5 +241,12 @@ func validateNormalizeUpdate(request *apimodel.WebPushSubscriptionUpdateRequest)
request.Data.Alerts.Reblog = *request.DataAlertsPendingReblog
}

if request.DataPolicy != nil {
request.Data.Policy = request.DataPolicy
}
if request.Data.Policy == nil {
request.Data.Policy = util.Ptr(apimodel.WebPushNotificationPolicyAll)
}

return nil
}
13 changes: 12 additions & 1 deletion internal/api/client/push/pushsubscriptionput_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ func (suite *PushTestSuite) putSubscription(
tokenFixtureName string,
alertsMention *bool,
alertsStatus *bool,
policy *string,
requestJson *string,
expectedHTTPStatus int,
) (*apimodel.WebPushSubscription, error) {
Expand Down Expand Up @@ -68,6 +69,9 @@ func (suite *PushTestSuite) putSubscription(
if alertsStatus != nil {
ctx.Request.Form["data[alerts][status]"] = []string{strconv.FormatBool(*alertsStatus)}
}
if policy != nil {
ctx.Request.Form["data[policy]"] = []string{*policy}
}
}

// trigger the handler
Expand Down Expand Up @@ -104,11 +108,13 @@ func (suite *PushTestSuite) TestPutSubscription() {

alertsMention := true
alertsStatus := false
policy := "followed"
subscription, err := suite.putSubscription(
accountFixtureName,
tokenFixtureName,
&alertsMention,
&alertsStatus,
&policy,
nil,
200,
)
Expand All @@ -120,6 +126,7 @@ func (suite *PushTestSuite) TestPutSubscription() {
suite.False(subscription.Alerts.Status)
// Omitted event types should default to off.
suite.False(subscription.Alerts.Favourite)
suite.Equal(apimodel.WebPushNotificationPolicyFollowed, subscription.Policy)
}
}

Expand All @@ -134,14 +141,16 @@ func (suite *PushTestSuite) TestPutSubscriptionJSON() {
"alerts": {
"mention": true,
"status": false
}
},
"policy": "followed"
}
}`
subscription, err := suite.putSubscription(
accountFixtureName,
tokenFixtureName,
nil,
nil,
nil,
&requestJson,
200,
)
Expand All @@ -153,6 +162,7 @@ func (suite *PushTestSuite) TestPutSubscriptionJSON() {
suite.False(subscription.Alerts.Status)
// Omitted event types should default to off.
suite.False(subscription.Alerts.Favourite)
suite.Equal(apimodel.WebPushNotificationPolicyFollowed, subscription.Policy)
}
}

Expand All @@ -170,6 +180,7 @@ func (suite *PushTestSuite) TestPutMissingSubscription() {
&alertsMention,
&alertsStatus,
nil,
nil,
404,
)
suite.NoError(err)
Expand Down
11 changes: 11 additions & 0 deletions internal/api/model/webpushsubscription.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ type WebPushSubscriptionUpdateRequest struct {
DataAlertsPendingFavourite *bool `form:"data[alerts][pending.favourite]" json:"-"`
DataAlertsPendingReply *bool `form:"data[alerts][pending.reply]" json:"-"`
DataAlertsPendingReblog *bool `form:"data[alerts][pending.reblog]" json:"-"`

DataPolicy *WebPushNotificationPolicy `form:"data[policy]" json:"-"`
}

// WebPushSubscriptionRequestData is the part of a Web Push subscription that can be changed after creation.
Expand All @@ -146,6 +148,9 @@ type WebPushSubscriptionUpdateRequest struct {
type WebPushSubscriptionRequestData struct {
// Alerts selects the specific events that this Web Push subscription will receive.
Alerts *WebPushSubscriptionAlerts `form:"-" json:"alerts"`

// Policy selects which accounts will trigger Web Push notifications.
Policy *WebPushNotificationPolicy `form:"-" json:"policy"`
}

// WebPushNotificationPolicy names sets of accounts that can generate notifications.
Expand All @@ -154,4 +159,10 @@ type WebPushNotificationPolicy string
const (
// WebPushNotificationPolicyAll allows all accounts to send notifications to the subscribing user.
WebPushNotificationPolicyAll WebPushNotificationPolicy = "all"
// WebPushNotificationPolicyFollowed allows accounts followed by the subscribing user to send notifications.
WebPushNotificationPolicyFollowed WebPushNotificationPolicy = "followed"
// WebPushNotificationPolicyFollower allows accounts following the subscribing user to send notifications.
WebPushNotificationPolicyFollower WebPushNotificationPolicy = "follower"
// WebPushNotificationPolicyNone doesn't allow any acounts to send notifications to the subscribing user.
WebPushNotificationPolicyNone WebPushNotificationPolicy = "none"
)
Loading

0 comments on commit 27844b7

Please sign in to comment.