Use packed notification flags

This commit is contained in:
Vyr Cossont 2024-12-25 17:40:23 -08:00
parent 86c686b5d6
commit 4bbd76abcf
8 changed files with 109 additions and 129 deletions

View file

@ -56,6 +56,7 @@ type Notification struct {
NotificationPendingReply NotificationType = 10 // NotificationPendingReply -- Someone has replied to a status of yours, which requires approval by you. NotificationPendingReply NotificationType = 10 // NotificationPendingReply -- Someone has replied to a status of yours, which requires approval by you.
NotificationPendingReblog NotificationType = 11 // NotificationPendingReblog -- Someone has boosted a status of yours, which requires approval by you. NotificationPendingReblog NotificationType = 11 // NotificationPendingReblog -- Someone has boosted a status of yours, which requires approval by you.
NotificationAdminReport NotificationType = 12 // NotificationAdminReport -- someone has submitted a new report to the instance. NotificationAdminReport NotificationType = 12 // NotificationAdminReport -- someone has submitted a new report to the instance.
NotificationTypeNumValues NotificationType = 13 // NotificationTypeNumValues -- 1 + number of max notification type
) )
// String returns a stringified, frontend API compatible form of NotificationType. // String returns a stringified, frontend API compatible form of NotificationType.

View file

@ -49,19 +49,44 @@ type WebPushSubscription struct {
// P256dh is a Base64-encoded Diffie-Hellman public key on the P-256 elliptic curve. // P256dh is a Base64-encoded Diffie-Hellman public key on the P-256 elliptic curve.
P256dh string `bun:",nullzero,notnull"` P256dh string `bun:",nullzero,notnull"`
// NotifyFollow and friends control which notifications are delivered to a given subscription. // NotificationFlags controls which notifications are delivered to a given subscription.
// Corresponds to NotificationType and model.PushSubscriptionAlerts. // Corresponds to model.PushSubscriptionAlerts.
NotifyFollow *bool `bun:",nullzero,notnull,default:false"` NotificationFlags WebPushSubscriptionNotificationFlags `bun:",notnull"`
NotifyFollowRequest *bool `bun:",nullzero,notnull,default:false"` }
NotifyFavourite *bool `bun:",nullzero,notnull,default:false"`
NotifyMention *bool `bun:",nullzero,notnull,default:false"` // WebPushSubscriptionNotificationFlags is a bitfield representation of a set of NotificationType.
NotifyReblog *bool `bun:",nullzero,notnull,default:false"` type WebPushSubscriptionNotificationFlags int64
NotifyPoll *bool `bun:",nullzero,notnull,default:false"`
NotifyStatus *bool `bun:",nullzero,notnull,default:false"` // WebPushSubscriptionNotificationFlagsFromSlice packs a slice of NotificationType into a WebPushSubscriptionNotificationFlags.
NotifyUpdate *bool `bun:",nullzero,notnull,default:false"` func WebPushSubscriptionNotificationFlagsFromSlice(notificationTypes []NotificationType) WebPushSubscriptionNotificationFlags {
NotifyAdminSignup *bool `bun:",nullzero,notnull,default:false"` var n WebPushSubscriptionNotificationFlags
NotifyAdminReport *bool `bun:",nullzero,notnull,default:false"` for _, notificationType := range notificationTypes {
NotifyPendingFavourite *bool `bun:",nullzero,notnull,default:false"` n.Set(notificationType, true)
NotifyPendingReply *bool `bun:",nullzero,notnull,default:false"` }
NotifyPendingReblog *bool `bun:",nullzero,notnull,default:false"` return n
}
// ToSlice unpacks a WebPushSubscriptionNotificationFlags into a slice of NotificationType.
func (n *WebPushSubscriptionNotificationFlags) ToSlice() []NotificationType {
notificationTypes := make([]NotificationType, 0, NotificationTypeNumValues)
for notificationType := NotificationUnknown; notificationType < NotificationTypeNumValues; notificationType++ {
if n.Get(notificationType) {
notificationTypes = append(notificationTypes, notificationType)
}
}
return notificationTypes
}
// Get tests to see if a given NotificationType is included in this set of flags.
func (n *WebPushSubscriptionNotificationFlags) Get(notificationType NotificationType) bool {
return *n&(1<<notificationType) != 0
}
// Set adds or removes a given NotificationType to or from this set of flags.
func (n *WebPushSubscriptionNotificationFlags) Set(notificationType NotificationType, value bool) {
if value {
*n |= 1 << notificationType
} else {
*n &= ^(1 << notificationType)
}
} }

View file

@ -48,26 +48,15 @@ func (p *Processor) CreateOrReplace(
// Insert a new one. // Insert a new one.
subscription := &gtsmodel.WebPushSubscription{ subscription := &gtsmodel.WebPushSubscription{
ID: id.NewULID(), ID: id.NewULID(),
AccountID: accountID, AccountID: accountID,
TokenID: tokenID, TokenID: tokenID,
Endpoint: request.Subscription.Endpoint, Endpoint: request.Subscription.Endpoint,
Auth: request.Subscription.Keys.Auth, Auth: request.Subscription.Keys.Auth,
P256dh: request.Subscription.Keys.P256dh, P256dh: request.Subscription.Keys.P256dh,
NotifyFollow: &request.Data.Alerts.Follow, NotificationFlags: alertsToNotificationFlags(request.Data.Alerts),
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 { if err := p.state.DB.PutWebPushSubscription(ctx, subscription); err != nil {
return nil, gtserror.NewErrorInternalError( return nil, gtserror.NewErrorInternalError(
gtserror.Newf("couldn't create Web Push subscription for token ID %s: %w", tokenID, err), gtserror.Newf("couldn't create Web Push subscription for token ID %s: %w", tokenID, err),

View file

@ -64,3 +64,25 @@ func (p *Processor) apiSubscription(ctx context.Context, subscription *gtsmodel.
return apiSubscription, nil 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)
// TODO: (Vyr) handle NotificationUpdate when edit patch is merged
//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

@ -49,35 +49,11 @@ func (p *Processor) Update(
} }
// Update it. // Update it.
subscription.NotifyFollow = &request.Data.Alerts.Follow subscription.NotificationFlags = alertsToNotificationFlags(request.Data.Alerts)
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( if err = p.state.DB.UpdateWebPushSubscription(
ctx, ctx,
subscription, subscription,
"notify_follow", "notification_flags",
"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 { ); err != nil {
return nil, gtserror.NewErrorInternalError( return nil, gtserror.NewErrorInternalError(
gtserror.Newf("couldn't update Web Push subscription for token ID %s: %w", tokenID, err), gtserror.Newf("couldn't update Web Push subscription for token ID %s: %w", tokenID, err),

View file

@ -2831,19 +2831,19 @@ func (c *Converter) WebPushSubscriptionToAPIWebPushSubscription(
Endpoint: subscription.Endpoint, Endpoint: subscription.Endpoint,
ServerKey: vapidKeyPair.Public, ServerKey: vapidKeyPair.Public,
Alerts: apimodel.WebPushSubscriptionAlerts{ Alerts: apimodel.WebPushSubscriptionAlerts{
Follow: *subscription.NotifyFollow, Follow: subscription.NotificationFlags.Get(gtsmodel.NotificationFollow),
FollowRequest: *subscription.NotifyFollowRequest, FollowRequest: subscription.NotificationFlags.Get(gtsmodel.NotificationFollowRequest),
Favourite: *subscription.NotifyFavourite, Favourite: subscription.NotificationFlags.Get(gtsmodel.NotificationFavourite),
Mention: *subscription.NotifyMention, Mention: subscription.NotificationFlags.Get(gtsmodel.NotificationMention),
Reblog: *subscription.NotifyReblog, Reblog: subscription.NotificationFlags.Get(gtsmodel.NotificationReblog),
Poll: *subscription.NotifyPoll, Poll: subscription.NotificationFlags.Get(gtsmodel.NotificationPoll),
Status: *subscription.NotifyStatus, Status: subscription.NotificationFlags.Get(gtsmodel.NotificationStatus),
Update: *subscription.NotifyUpdate, Update: false, // TODO: (Vyr) handle NotificationUpdate when edit patch is merged
AdminSignup: *subscription.NotifyAdminSignup, AdminSignup: subscription.NotificationFlags.Get(gtsmodel.NotificationAdminSignup),
AdminReport: *subscription.NotifyAdminReport, AdminReport: subscription.NotificationFlags.Get(gtsmodel.NotificationAdminReport),
PendingFavourite: *subscription.NotifyPendingFavourite, PendingFavourite: subscription.NotificationFlags.Get(gtsmodel.NotificationPendingFave),
PendingReply: *subscription.NotifyPendingReply, PendingReply: subscription.NotificationFlags.Get(gtsmodel.NotificationPendingReply),
PendingReblog: *subscription.NotifyPendingReblog, PendingReblog: subscription.NotificationFlags.Get(gtsmodel.NotificationPendingReblog),
}, },
}, nil }, nil
} }

View file

@ -85,44 +85,9 @@ func (r *realSender) Send(
relevantSubscriptions := make([]*gtsmodel.WebPushSubscription, 0, len(subscriptions)) relevantSubscriptions := make([]*gtsmodel.WebPushSubscription, 0, len(subscriptions))
for _, subscription := range subscriptions { for _, subscription := range subscriptions {
// Check whether this subscription wants this type of notification. // Check whether this subscription wants this type of notification.
notify := false if subscription.NotificationFlags.Get(notification.NotificationType) {
switch notification.NotificationType { relevantSubscriptions = append(relevantSubscriptions, subscription)
case gtsmodel.NotificationFollow:
notify = *subscription.NotifyFollow
case gtsmodel.NotificationFollowRequest:
notify = *subscription.NotifyFollowRequest
case gtsmodel.NotificationMention:
notify = *subscription.NotifyMention
case gtsmodel.NotificationReblog:
notify = *subscription.NotifyReblog
case gtsmodel.NotificationFavourite:
notify = *subscription.NotifyFavourite
case gtsmodel.NotificationPoll:
notify = *subscription.NotifyPoll
case gtsmodel.NotificationStatus:
notify = *subscription.NotifyStatus
case gtsmodel.NotificationAdminSignup:
notify = *subscription.NotifyAdminSignup
case gtsmodel.NotificationAdminReport:
notify = *subscription.NotifyAdminReport
case gtsmodel.NotificationPendingFave:
notify = *subscription.NotifyPendingFavourite
case gtsmodel.NotificationPendingReply:
notify = *subscription.NotifyPendingReply
case gtsmodel.NotificationPendingReblog:
notify = *subscription.NotifyPendingReblog
default:
log.Errorf(
ctx,
"notification type not supported by Web Push subscriptions: %v",
notification.NotificationType,
)
continue
} }
if !notify {
continue
}
relevantSubscriptions = append(relevantSubscriptions, subscription)
} }
if len(relevantSubscriptions) == 0 { if len(relevantSubscriptions) == 0 {
return nil return nil

View file

@ -3479,25 +3479,27 @@ func NewTestUserMutes() map[string]*gtsmodel.UserMute {
func NewTestWebPushSubscriptions() map[string]*gtsmodel.WebPushSubscription { func NewTestWebPushSubscriptions() map[string]*gtsmodel.WebPushSubscription {
return map[string]*gtsmodel.WebPushSubscription{ return map[string]*gtsmodel.WebPushSubscription{
"local_account_1_token_1": { "local_account_1_token_1": {
ID: "01G65Z755AFWAKHE12NY0CQ9FH", ID: "01G65Z755AFWAKHE12NY0CQ9FH",
AccountID: "01F8MH1H7YV1Z7D2C8K2730QBF", AccountID: "01F8MH1H7YV1Z7D2C8K2730QBF",
TokenID: "01F8MGTQW4DKTDF8SW5CT9HYGA", TokenID: "01F8MGTQW4DKTDF8SW5CT9HYGA",
Endpoint: "https://example.test/push", Endpoint: "https://example.test/push",
Auth: "cgna/fzrYLDQyPf5hD7IsA==", Auth: "cgna/fzrYLDQyPf5hD7IsA==",
P256dh: "BMYVItYVOX+AHBdtA62Q0i6c+F7MV2Gia3aoDr8mvHkuPBNIOuTLDfmFcnBqoZcQk6BtLcIONbxhHpy2R+mYIUY=", P256dh: "BMYVItYVOX+AHBdtA62Q0i6c+F7MV2Gia3aoDr8mvHkuPBNIOuTLDfmFcnBqoZcQk6BtLcIONbxhHpy2R+mYIUY=",
NotifyFollow: util.Ptr(true), NotificationFlags: gtsmodel.WebPushSubscriptionNotificationFlagsFromSlice([]gtsmodel.NotificationType{
NotifyFollowRequest: util.Ptr(true), gtsmodel.NotificationFollow,
NotifyFavourite: util.Ptr(true), gtsmodel.NotificationFollowRequest,
NotifyMention: util.Ptr(true), gtsmodel.NotificationFavourite,
NotifyReblog: util.Ptr(true), gtsmodel.NotificationMention,
NotifyPoll: util.Ptr(true), gtsmodel.NotificationReblog,
NotifyStatus: util.Ptr(true), gtsmodel.NotificationPoll,
NotifyUpdate: util.Ptr(true), gtsmodel.NotificationStatus,
NotifyAdminSignup: util.Ptr(true), // TODO: (Vyr) add NotificationUpdate when edit patch is merged
NotifyAdminReport: util.Ptr(true), gtsmodel.NotificationAdminSignup,
NotifyPendingFavourite: util.Ptr(true), gtsmodel.NotificationAdminReport,
NotifyPendingReply: util.Ptr(true), gtsmodel.NotificationPendingFave,
NotifyPendingReblog: util.Ptr(true), gtsmodel.NotificationPendingReply,
gtsmodel.NotificationPendingReblog,
}),
}, },
} }
} }