", types.EventLabel[event_title]))
- unsubURL := "https://" + utils.Config.Frontend.SiteDomain + "/notifications/unsubscribe"
i := 0
for _, n := range ns {
// Find all unique notification titles for the subject
@@ -737,64 +737,6 @@ func queueEmailNotifications(notificationsByUserID types.NotificationsPerUserId,
notificationTitles = append(notificationTitles, title)
}
- // TODO: this is bad and will break in case there are a lot of unsubscribe hashes to generate
- // the unsubscribe hash should be set when we add the subscription to the db
- unsubHash := n.GetUnsubscribeHash()
- if unsubHash == "" {
- id := n.GetSubscriptionID()
-
- tx, err := db.FrontendWriterDB.Beginx()
- if err != nil {
- log.Error(err, "error starting transaction", 0)
- }
- var sub types.Subscription
- err = tx.Get(&sub, `
- SELECT
- id,
- user_id,
- event_name,
- event_filter,
- last_sent_ts,
- last_sent_epoch,
- created_ts,
- created_epoch,
- event_threshold
- FROM users_subscriptions
- WHERE id = $1
- `, id)
- if err != nil {
- log.Error(err, "error getting user subscription by subscription id", 0)
- utils.Rollback(tx)
- continue
- }
-
- raw := fmt.Sprintf("%v%v%v%v", sub.ID, sub.UserID, sub.EventName, sub.CreatedTime)
- digest := sha256.Sum256([]byte(raw))
-
- _, err = tx.Exec("UPDATE users_subscriptions set unsubscribe_hash = $1 WHERE id = $2", digest[:], id)
- if err != nil {
- log.Error(err, "error updating users subscriptions table with unsubscribe hash", 0)
- utils.Rollback(tx)
- continue
- }
-
- err = tx.Commit()
- if err != nil {
- log.Error(err, "error committing transaction to update users subscriptions with an unsubscribe hash", 0)
- utils.Rollback(tx)
- continue
- }
-
- unsubHash = hex.EncodeToString(digest[:])
- }
- if i == 0 {
- unsubURL += "?hash=" + html.EscapeString(unsubHash)
- } else {
- unsubURL += "&hash=" + html.EscapeString(unsubHash)
- }
- //nolint:gosec // this is a static string
- msg.UnsubURL = template.HTML(fmt.Sprintf(`Unsubscribe`, unsubURL))
-
if event != types.SyncCommitteeSoon {
// SyncCommitteeSoon notifications are summed up in getEventInfo for all validators
//nolint:gosec // this is a static string
@@ -1150,6 +1092,8 @@ func sendDiscordNotifications(useDB *sqlx.DB) error {
notifMap[n.Content.Webhook.ID] = append(notifMap[n.Content.Webhook.ID], n)
}
for _, webhook := range webhookMap {
+ // todo: this has the potential to spin up thousands of go routines
+ // should use an errgroup instead if we decide to keep the aproach
go func(webhook types.UserWebhook, reqs []types.TransitDiscord) {
defer func() {
// update retries counters in db based on end result
@@ -1780,7 +1724,7 @@ func collectValidatorGotSlashedNotifications(notificationsByUserID types.Notific
resultsLen := len(dbResult)
for i, event := range dbResult {
// TODO: clarify why we need the id here?!
- query += fmt.Sprintf(`SELECT %d AS ref, id, user_id, ENCODE(unsubscribe_hash, 'hex') AS unsubscribe_hash, event_name from users_subscriptions where event_name = $1 AND event_filter = '%x'`, i, event.SlashedValidatorPubkey)
+ query += fmt.Sprintf(`SELECT %d AS ref, id, user_id, event_name from users_subscriptions where event_name = $1 AND event_filter = '%x'`, i, event.SlashedValidatorPubkey)
if i < resultsLen-1 {
query += " UNION "
}
@@ -1791,11 +1735,10 @@ func collectValidatorGotSlashedNotifications(notificationsByUserID types.Notific
}
var subscribers []struct {
- Ref uint64 `db:"ref"`
- Id uint64 `db:"id"`
- UserId types.UserId `db:"user_id"`
- UnsubscribeHash sql.NullString `db:"unsubscribe_hash"`
- EventName types.EventName `db:"event_name"`
+ Ref uint64 `db:"ref"`
+ Id uint64 `db:"id"`
+ UserId types.UserId `db:"user_id"`
+ EventName types.EventName `db:"event_name"`
}
name := string(types.ValidatorGotSlashedEventName)
@@ -1814,12 +1757,11 @@ func collectValidatorGotSlashedNotifications(notificationsByUserID types.Notific
n := &validatorGotSlashedNotification{
NotificationBaseImpl: types.NotificationBaseImpl{
- SubscriptionID: sub.Id,
- UserID: sub.UserId,
- Epoch: event.Epoch,
- EventFilter: hex.EncodeToString(event.SlashedValidatorPubkey),
- UnsubscribeHash: sub.UnsubscribeHash,
- EventName: sub.EventName,
+ SubscriptionID: sub.Id,
+ UserID: sub.UserId,
+ Epoch: event.Epoch,
+ EventFilter: hex.EncodeToString(event.SlashedValidatorPubkey),
+ EventName: sub.EventName,
},
Slasher: event.SlasherIndex,
Reason: event.Reason,
@@ -1894,11 +1836,10 @@ func collectWithdrawalNotifications(notificationsByUserID types.NotificationsPer
// log.Infof("creating %v notification for validator %v in epoch %v", types.ValidatorReceivedWithdrawalEventName, event.ValidatorIndex, epoch)
n := &validatorWithdrawalNotification{
NotificationBaseImpl: types.NotificationBaseImpl{
- SubscriptionID: *sub.ID,
- UserID: *sub.UserID,
- EventFilter: hex.EncodeToString(event.Pubkey),
- UnsubscribeHash: sub.UnsubscribeHash,
- EventName: sub.EventName,
+ SubscriptionID: *sub.ID,
+ UserID: *sub.UserID,
+ EventFilter: hex.EncodeToString(event.Pubkey),
+ EventName: sub.EventName,
},
ValidatorIndex: event.ValidatorIndex,
Epoch: epoch,
@@ -2023,12 +1964,11 @@ func collectEthClientNotifications(notificationsByUserID types.NotificationsPerU
for _, sub := range subs {
n := ðClientNotification{
NotificationBaseImpl: types.NotificationBaseImpl{
- SubscriptionID: *sub.ID,
- UserID: *sub.UserID,
- Epoch: sub.CreatedEpoch,
- EventFilter: sub.EventFilter,
- UnsubscribeHash: sub.UnsubscribeHash,
- EventName: sub.EventName,
+ SubscriptionID: *sub.ID,
+ UserID: *sub.UserID,
+ Epoch: sub.CreatedEpoch,
+ EventFilter: sub.EventFilter,
+ EventName: sub.EventName,
},
EthClient: client.Name,
}
@@ -2221,12 +2161,11 @@ func collectMonitoringMachine(
for _, r := range result {
n := &monitorMachineNotification{
NotificationBaseImpl: types.NotificationBaseImpl{
- SubscriptionID: *r.ID,
- UserID: *r.UserID,
- EventName: r.EventName,
- Epoch: epoch,
- UnsubscribeHash: r.UnsubscribeHash,
- EventFilter: r.EventFilter,
+ SubscriptionID: *r.ID,
+ UserID: *r.UserID,
+ EventName: r.EventName,
+ Epoch: epoch,
+ EventFilter: r.EventFilter,
},
MachineName: r.EventFilter,
}
@@ -2386,12 +2325,11 @@ func collectTaxReportNotificationNotifications(notificationsByUserID types.Notif
for _, sub := range subs {
n := &taxReportNotification{
NotificationBaseImpl: types.NotificationBaseImpl{
- SubscriptionID: *sub.ID,
- UserID: *sub.UserID,
- Epoch: sub.CreatedEpoch,
- EventFilter: sub.EventFilter,
- UnsubscribeHash: sub.UnsubscribeHash,
- EventName: sub.EventName,
+ SubscriptionID: *sub.ID,
+ UserID: *sub.UserID,
+ Epoch: sub.CreatedEpoch,
+ EventFilter: sub.EventFilter,
+ EventName: sub.EventName,
},
}
notificationsByUserID.AddNotification(n)
@@ -2456,12 +2394,11 @@ func collectNetworkNotifications(notificationsByUserID types.NotificationsPerUse
for _, sub := range subs {
n := &networkNotification{
NotificationBaseImpl: types.NotificationBaseImpl{
- SubscriptionID: *sub.ID,
- UserID: *sub.UserID,
- Epoch: sub.CreatedEpoch,
- EventFilter: sub.EventFilter,
- UnsubscribeHash: sub.UnsubscribeHash,
- EventName: sub.EventName,
+ SubscriptionID: *sub.ID,
+ UserID: *sub.UserID,
+ Epoch: sub.CreatedEpoch,
+ EventFilter: sub.EventFilter,
+ EventName: sub.EventName,
},
}
@@ -2550,12 +2487,11 @@ func collectRocketpoolComissionNotifications(notificationsByUserID types.Notific
for _, sub := range subs {
n := &rocketpoolNotification{
NotificationBaseImpl: types.NotificationBaseImpl{
- SubscriptionID: *sub.ID,
- UserID: *sub.UserID,
- Epoch: sub.CreatedEpoch,
- EventFilter: sub.EventFilter,
- EventName: sub.EventName,
- UnsubscribeHash: sub.UnsubscribeHash,
+ SubscriptionID: *sub.ID,
+ UserID: *sub.UserID,
+ Epoch: sub.CreatedEpoch,
+ EventFilter: sub.EventFilter,
+ EventName: sub.EventName,
},
ExtraData: strconv.FormatInt(int64(fee*100), 10) + "%",
}
@@ -2603,12 +2539,11 @@ func collectRocketpoolRewardClaimRoundNotifications(notificationsByUserID types.
for _, sub := range subs {
n := &rocketpoolNotification{
NotificationBaseImpl: types.NotificationBaseImpl{
- SubscriptionID: *sub.ID,
- UserID: *sub.UserID,
- Epoch: sub.CreatedEpoch,
- EventFilter: sub.EventFilter,
- EventName: sub.EventName,
- UnsubscribeHash: sub.UnsubscribeHash,
+ SubscriptionID: *sub.ID,
+ UserID: *sub.UserID,
+ Epoch: sub.CreatedEpoch,
+ EventFilter: sub.EventFilter,
+ EventName: sub.EventName,
},
}
@@ -2738,12 +2673,11 @@ func collectRocketpoolRPLCollateralNotifications(notificationsByUserID types.Not
n := &rocketpoolNotification{
NotificationBaseImpl: types.NotificationBaseImpl{
- SubscriptionID: *sub.ID,
- UserID: *sub.UserID,
- Epoch: epoch,
- EventFilter: sub.EventFilter,
- EventName: sub.EventName,
- UnsubscribeHash: sub.UnsubscribeHash,
+ SubscriptionID: *sub.ID,
+ UserID: *sub.UserID,
+ Epoch: epoch,
+ EventFilter: sub.EventFilter,
+ EventName: sub.EventName,
},
ExtraData: strings.TrimRight(strings.TrimRight(fmt.Sprintf("%.2f", threshold*100), "0"), "."),
}
@@ -2839,12 +2773,11 @@ func collectSyncCommittee(notificationsByUserID types.NotificationsPerUserId, ep
for _, sub := range subs {
n := &rocketpoolNotification{
NotificationBaseImpl: types.NotificationBaseImpl{
- SubscriptionID: *sub.ID,
- UserID: *sub.UserID,
- Epoch: epoch,
- EventFilter: sub.EventFilter,
- EventName: sub.EventName,
- UnsubscribeHash: sub.UnsubscribeHash,
+ SubscriptionID: *sub.ID,
+ UserID: *sub.UserID,
+ Epoch: epoch,
+ EventFilter: sub.EventFilter,
+ EventName: sub.EventName,
},
ExtraData: fmt.Sprintf("%v|%v|%v", mapping[sub.EventFilter], nextPeriod*utils.Config.Chain.ClConfig.EpochsPerSyncCommitteePeriod, (nextPeriod+1)*utils.Config.Chain.ClConfig.EpochsPerSyncCommitteePeriod),
}
From c85a5e5ff247b763c609fb46ad2d7e1a6a5f472f Mon Sep 17 00:00:00 2001
From: peter <1674920+peterbitfly@users.noreply.github.com>
Date: Tue, 9 Jul 2024 12:25:51 +0200
Subject: [PATCH 12/94] add handling of orphaned block notifications
---
backend/pkg/notification/notifications.go | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/backend/pkg/notification/notifications.go b/backend/pkg/notification/notifications.go
index 2d7b6ff7c..5ca1a1108 100644
--- a/backend/pkg/notification/notifications.go
+++ b/backend/pkg/notification/notifications.go
@@ -1306,6 +1306,8 @@ func (n *validatorProposalNotification) GetInfo(includeUrl bool) string {
generalPart = fmt.Sprintf(`Validator %s proposed block at slot %s with %v %v execution reward.`, vali, slot, n.Reward, utils.Config.Frontend.ElCurrency)
case 2:
generalPart = fmt.Sprintf(`Validator %s missed a block proposal at slot %s.`, vali, slot)
+ case 3:
+ generalPart = fmt.Sprintf(`Validator %s had an orphaned block proposal at slot %s.`, vali, slot)
}
return generalPart + suffix
}
@@ -1318,6 +1320,8 @@ func (n *validatorProposalNotification) GetTitle() string {
return "New Block Proposal"
case 2:
return "Block Proposal Missed"
+ case 3:
+ return "Block Proposal Missed (Orphaned)"
}
return "-"
}
@@ -1331,6 +1335,8 @@ func (n *validatorProposalNotification) GetInfoMarkdown() string {
generalPart = fmt.Sprintf(`Validator [%[2]v](https://%[1]v/validator/%[2]v) proposed a new block at slot [%[3]v](https://%[1]v/slot/%[3]v) with %[4]v %[5]v execution reward.`, utils.Config.Frontend.SiteDomain, n.ValidatorIndex, n.Slot, n.Reward, utils.Config.Frontend.ElCurrency)
case 2:
generalPart = fmt.Sprintf(`Validator [%[2]v](https://%[1]v/validator/%[2]v) missed a block proposal at slot [%[3]v](https://%[1]v/slot/%[3]v).`, utils.Config.Frontend.SiteDomain, n.ValidatorIndex, n.Slot)
+ case 3:
+ generalPart = fmt.Sprintf(`Validator [%[2]v](https://%[1]v/validator/%[2]v) had an orphaned block proposal at slot [%[3]v](https://%[1]v/slot/%[3]v).`, utils.Config.Frontend.SiteDomain, n.ValidatorIndex, n.Slot)
}
return generalPart
From 2ca40f7f1633ee5447e5bd81cb673d2b90cd9c33 Mon Sep 17 00:00:00 2001
From: peter <1674920+peterbitfly@users.noreply.github.com>
Date: Tue, 9 Jul 2024 12:50:44 +0200
Subject: [PATCH 13/94] implement code review changes
---
backend/pkg/commons/db/db.go | 18 +--
backend/pkg/commons/types/exporter.go | 9 ++
backend/pkg/commons/types/frontend.go | 7 +-
backend/pkg/notification/db.go | 1 +
backend/pkg/notification/notifications.go | 142 +++++++++++-----------
5 files changed, 88 insertions(+), 89 deletions(-)
diff --git a/backend/pkg/commons/db/db.go b/backend/pkg/commons/db/db.go
index f85f3641e..fe13e3e67 100644
--- a/backend/pkg/commons/db/db.go
+++ b/backend/pkg/commons/db/db.go
@@ -954,22 +954,8 @@ func GetTotalEligibleEther() (uint64, error) {
}
// GetValidatorsGotSlashed returns the validators that got slashed after `epoch` either by an attestation violation or a proposer violation
-func GetValidatorsGotSlashed(epoch uint64) ([]struct {
- Epoch uint64 `db:"epoch"`
- SlasherIndex uint64 `db:"slasher"`
- SlasherPubkey string `db:"slasher_pubkey"`
- SlashedValidatorIndex uint64 `db:"slashedvalidator"`
- SlashedValidatorPubkey []byte `db:"slashedvalidator_pubkey"`
- Reason string `db:"reason"`
-}, error) {
- var dbResult []struct {
- Epoch uint64 `db:"epoch"`
- SlasherIndex uint64 `db:"slasher"`
- SlasherPubkey string `db:"slasher_pubkey"`
- SlashedValidatorIndex uint64 `db:"slashedvalidator"`
- SlashedValidatorPubkey []byte `db:"slashedvalidator_pubkey"`
- Reason string `db:"reason"`
- }
+func GetValidatorsGotSlashed(epoch uint64) ([]*types.SlashingInfo, error) {
+ var dbResult []*types.SlashingInfo
err := ReaderDb.Select(&dbResult, `
WITH
slashings AS (
diff --git a/backend/pkg/commons/types/exporter.go b/backend/pkg/commons/types/exporter.go
index 5a3f3a397..197638472 100644
--- a/backend/pkg/commons/types/exporter.go
+++ b/backend/pkg/commons/types/exporter.go
@@ -709,3 +709,12 @@ type RedisCachedValidatorsMapping struct {
Epoch Epoch
Mapping []*CachedValidator
}
+
+type SlashingInfo struct {
+ Epoch uint64 `db:"epoch"`
+ SlasherIndex uint64 `db:"slasher"`
+ SlasherPubkey string `db:"slasher_pubkey"`
+ SlashedValidatorIndex uint64 `db:"slashedvalidator"`
+ SlashedValidatorPubkey []byte `db:"slashedvalidator_pubkey"`
+ Reason string `db:"reason"`
+}
diff --git a/backend/pkg/commons/types/frontend.go b/backend/pkg/commons/types/frontend.go
index 9c8f45c04..5c90a8be3 100644
--- a/backend/pkg/commons/types/frontend.go
+++ b/backend/pkg/commons/types/frontend.go
@@ -33,9 +33,10 @@ func (npui NotificationsPerUserId) AddNotification(n Notification) {
if n.GetEventName() == "" {
log.Fatal(fmt.Errorf("Notification event name is empty"), fmt.Sprintf("Notification: %v", n), 0)
}
- if n.GetEventFilter() == "" {
- log.Fatal(fmt.Errorf("Notification event filter is empty"), fmt.Sprintf("Notification: %v", n), 0)
- }
+ // next check is disabled as there are events that do not require a filter (rocketpool, network events)
+ // if n.GetEventFilter() == "" {
+ // log.Fatal(fmt.Errorf("Notification event filter is empty"), fmt.Sprintf("Notification: %v", n), 0)
+ // }
if _, ok := npui[n.GetUserId()]; !ok {
npui[n.GetUserId()] = make(map[EventName]map[EventFilter]Notification)
diff --git a/backend/pkg/notification/db.go b/backend/pkg/notification/db.go
index cbcb9d497..7b8948d34 100644
--- a/backend/pkg/notification/db.go
+++ b/backend/pkg/notification/db.go
@@ -12,6 +12,7 @@ import (
// Map key corresponds to the event filter which can be
// a validator pubkey or an eth1 address (for RPL notifications)
// or a list of validators for the tax report notifications
+// or a machine name for machine notifications or a eth client name for ethereum client update notifications
// optionally it is possible to set a filter on the last sent ts and the event filter
// fields
func GetSubsForEventFilter(eventName types.EventName, lastSentFilter string, lastSentFilterArgs []interface{}, eventFilters []string) (map[string][]types.Subscription, error) {
diff --git a/backend/pkg/notification/notifications.go b/backend/pkg/notification/notifications.go
index 5ca1a1108..6b6e0ea0c 100644
--- a/backend/pkg/notification/notifications.go
+++ b/backend/pkg/notification/notifications.go
@@ -1726,55 +1726,44 @@ func collectValidatorGotSlashedNotifications(notificationsByUserID types.Notific
if err != nil {
return fmt.Errorf("error getting slashed validators from database, err: %w", err)
}
- query := ""
- resultsLen := len(dbResult)
- for i, event := range dbResult {
- // TODO: clarify why we need the id here?!
- query += fmt.Sprintf(`SELECT %d AS ref, id, user_id, event_name from users_subscriptions where event_name = $1 AND event_filter = '%x'`, i, event.SlashedValidatorPubkey)
- if i < resultsLen-1 {
- query += " UNION "
- }
- }
-
- if query == "" {
- return nil
- }
-
- var subscribers []struct {
- Ref uint64 `db:"ref"`
- Id uint64 `db:"id"`
- UserId types.UserId `db:"user_id"`
- EventName types.EventName `db:"event_name"`
+ slashedPubkeys := make([]string, 0, len(dbResult))
+ pubkeyToSlashingInfoMap := make(map[string]*types.SlashingInfo)
+ for _, event := range dbResult {
+ pubkeyStr := hex.EncodeToString(event.SlashedValidatorPubkey)
+ slashedPubkeys = append(slashedPubkeys, pubkeyStr)
+ pubkeyToSlashingInfoMap[pubkeyStr] = event
}
- name := string(types.ValidatorGotSlashedEventName)
- if utils.Config.Chain.ClConfig.ConfigName != "" {
- name = utils.Config.Chain.ClConfig.ConfigName + ":" + name
- }
- err = db.FrontendWriterDB.Select(&subscribers, query, name)
+ subscribedUsers, err := GetSubsForEventFilter(types.ValidatorGotSlashedEventName, "", nil, slashedPubkeys)
if err != nil {
- return fmt.Errorf("error querying subscribers, err: %w", err)
+ return fmt.Errorf("failed to get subs for %v: %v", types.ValidatorGotSlashedEventName, err)
}
- for _, sub := range subscribers {
- event := dbResult[sub.Ref]
+ for _, subs := range subscribedUsers {
+ for _, sub := range subs {
- log.Infof("creating %v notification for validator %v in epoch %v", event.SlashedValidatorPubkey, event.Reason, epoch)
+ event := pubkeyToSlashingInfoMap[sub.EventFilter]
+ if event == nil {
+ log.Error(fmt.Errorf("error retrieving slashing info for public key %s", sub.EventFilter), "", 0)
+ continue
+ }
+ log.Infof("creating %v notification for validator %v in epoch %v", event.Reason, sub.EventFilter, epoch)
- n := &validatorGotSlashedNotification{
- NotificationBaseImpl: types.NotificationBaseImpl{
- SubscriptionID: sub.Id,
- UserID: sub.UserId,
- Epoch: event.Epoch,
- EventFilter: hex.EncodeToString(event.SlashedValidatorPubkey),
- EventName: sub.EventName,
- },
- Slasher: event.SlasherIndex,
- Reason: event.Reason,
- ValidatorIndex: event.SlashedValidatorIndex,
+ n := &validatorGotSlashedNotification{
+ NotificationBaseImpl: types.NotificationBaseImpl{
+ SubscriptionID: *sub.ID,
+ UserID: *sub.UserID,
+ Epoch: epoch,
+ EventFilter: sub.EventFilter,
+ EventName: sub.EventName,
+ },
+ Slasher: event.SlasherIndex,
+ Reason: event.Reason,
+ ValidatorIndex: event.SlashedValidatorIndex,
+ }
+ notificationsByUserID.AddNotification(n)
+ metrics.NotificationsCollected.WithLabelValues(string(n.GetEventName())).Inc()
}
- notificationsByUserID.AddNotification(n)
- metrics.NotificationsCollected.WithLabelValues(string(n.GetEventName())).Inc()
}
return nil
@@ -2077,28 +2066,39 @@ func collectMonitoringMachine(
notifyConditionFulfilled func(subscribeData *types.Subscription, machineData *types.MachineMetricSystemUser) bool,
epoch uint64,
) error {
- var allSubscribed []*types.Subscription
// event_filter == machine name
+
+ dbResult, err := GetSubsForEventFilter(
+ eventName,
+ "us.created_epoch <= ? AND (us.last_sent_epoch < (? - ?) OR us.last_sent_epoch IS NULL)",
+ []interface{}{epoch, epoch, epochWaitInBetween},
+ nil,
+ )
+
// TODO: clarify why we need grouping here?!
- err := db.FrontendWriterDB.Select(&allSubscribed,
- `SELECT
- us.user_id,
- max(us.id) AS id,
- ENCODE((array_agg(us.unsubscribe_hash))[1], 'hex') AS unsubscribe_hash,
- event_filter,
- COALESCE(event_threshold, 0) AS event_threshold
- FROM users_subscriptions us
- WHERE us.event_name = $1 AND us.created_epoch <= $2
- AND (us.last_sent_epoch < ($2 - $3) OR us.last_sent_epoch IS NULL)
- group by us.user_id, event_filter, event_threshold`,
- eventName, epoch, epochWaitInBetween)
+ // err := db.FrontendWriterDB.Select(&allSubscribed,
+ // `SELECT
+ // us.user_id,
+ // max(us.id) AS id,
+ // ENCODE((array_agg(us.unsubscribe_hash))[1], 'hex') AS unsubscribe_hash,
+ // event_filter,
+ // COALESCE(event_threshold, 0) AS event_threshold
+ // FROM users_subscriptions us
+ // WHERE us.event_name = $1 AND us.created_epoch <= $2
+ // AND (us.last_sent_epoch < ($2 - $3) OR us.last_sent_epoch IS NULL)
+ // group by us.user_id, event_filter, event_threshold`,
+ // eventName, epoch, epochWaitInBetween)
if err != nil {
return err
}
rowKeys := gcp_bigtable.RowList{}
- for _, data := range allSubscribed {
- rowKeys = append(rowKeys, db.BigtableClient.GetMachineRowKey(*data.UserID, "system", data.EventFilter))
+ totalSubscribed := 0
+ for _, data := range dbResult {
+ for _, sub := range data {
+ rowKeys = append(rowKeys, db.BigtableClient.GetMachineRowKey(*sub.UserID, "system", sub.EventFilter))
+ totalSubscribed++
+ }
}
machineDataOfSubscribed, err := db.BigtableClient.GetMachineMetricsForNotifications(rowKeys)
@@ -2107,20 +2107,22 @@ func collectMonitoringMachine(
}
var result []*types.Subscription
- for _, data := range allSubscribed {
- localData := data // Create a local copy of the data variable
- machineMap, found := machineDataOfSubscribed[*localData.UserID]
- if !found {
- continue
- }
- currentMachineData, found := machineMap[localData.EventFilter]
- if !found {
- continue
- }
+ for _, data := range dbResult {
+ for _, sub := range data {
+ localData := sub // Create a local copy of the data variable
+ machineMap, found := machineDataOfSubscribed[*localData.UserID]
+ if !found {
+ continue
+ }
+ currentMachineData, found := machineMap[localData.EventFilter]
+ if !found {
+ continue
+ }
- //logrus.Infof("currentMachineData %v | %v | %v | %v", currentMachine.CurrentDataInsertTs, currentMachine.CompareDataInsertTs, currentMachine.UserID, currentMachine.Machine)
- if notifyConditionFulfilled(localData, currentMachineData) {
- result = append(result, localData)
+ //logrus.Infof("currentMachineData %v | %v | %v | %v", currentMachine.CurrentDataInsertTs, currentMachine.CompareDataInsertTs, currentMachine.UserID, currentMachine.Machine)
+ if notifyConditionFulfilled(&localData, currentMachineData) {
+ result = append(result, &localData)
+ }
}
}
@@ -2158,7 +2160,7 @@ func collectMonitoringMachine(
subRatioThreshold = subFirstRatioThreshold
isFirstNotificationCheck = false
}
- if float64(len(result))/float64(len(allSubscribed)) >= subRatioThreshold {
+ if float64(len(result))/float64(totalSubscribed) >= subRatioThreshold {
log.Error(nil, fmt.Errorf("error too many users would be notified concerning: %v", eventName), 0)
return nil
}
From f1a2ef83d0f1c545354f8337402440b9cd621c5e Mon Sep 17 00:00:00 2001
From: peter <1674920+peterbitfly@users.noreply.github.com>
Date: Tue, 9 Jul 2024 12:53:53 +0200
Subject: [PATCH 14/94] please linter
---
backend/pkg/notification/notifications.go | 1 -
1 file changed, 1 deletion(-)
diff --git a/backend/pkg/notification/notifications.go b/backend/pkg/notification/notifications.go
index 6b6e0ea0c..51cb926ae 100644
--- a/backend/pkg/notification/notifications.go
+++ b/backend/pkg/notification/notifications.go
@@ -1741,7 +1741,6 @@ func collectValidatorGotSlashedNotifications(notificationsByUserID types.Notific
for _, subs := range subscribedUsers {
for _, sub := range subs {
-
event := pubkeyToSlashingInfoMap[sub.EventFilter]
if event == nil {
log.Error(fmt.Errorf("error retrieving slashing info for public key %s", sub.EventFilter), "", 0)
From 274816226cd18f5dc8e1cee2bab64fe6cad92883 Mon Sep 17 00:00:00 2001
From: peter <1674920+peterbitfly@users.noreply.github.com>
Date: Wed, 10 Jul 2024 10:29:46 +0200
Subject: [PATCH 15/94] remove internal state for notifications
---
backend/pkg/commons/types/frontend.go | 12 ++++----
backend/pkg/notification/db.go | 1 -
backend/pkg/notification/notifications.go | 35 ++++++++++++-----------
3 files changed, 24 insertions(+), 24 deletions(-)
diff --git a/backend/pkg/commons/types/frontend.go b/backend/pkg/commons/types/frontend.go
index 5c90a8be3..767aca38b 100644
--- a/backend/pkg/commons/types/frontend.go
+++ b/backend/pkg/commons/types/frontend.go
@@ -341,12 +341,12 @@ type Subscription struct {
LastSent *time.Time `db:"last_sent_ts"`
LastEpoch *uint64 `db:"last_sent_epoch"`
// Channels pq.StringArray `db:"channels"`
- CreatedTime time.Time `db:"created_ts"`
- CreatedEpoch uint64 `db:"created_epoch"`
- EventThreshold float64 `db:"event_threshold"`
- State sql.NullString `db:"internal_state" swaggertype:"string"`
- GroupId *int64
- DashboardId *int64
+ CreatedTime time.Time `db:"created_ts"`
+ CreatedEpoch uint64 `db:"created_epoch"`
+ EventThreshold float64 `db:"event_threshold"`
+ // State sql.NullString `db:"internal_state" swaggertype:"string"`
+ GroupId *int64
+ DashboardId *int64
}
type UserId uint64
diff --git a/backend/pkg/notification/db.go b/backend/pkg/notification/db.go
index 7b8948d34..4363c12f7 100644
--- a/backend/pkg/notification/db.go
+++ b/backend/pkg/notification/db.go
@@ -39,7 +39,6 @@ func GetSubsForEventFilter(eventName types.EventName, lastSentFilter string, las
goqu.C("last_sent_epoch"),
goqu.C("created_epoch"),
goqu.C("event_threshold"),
- goqu.C("internal_state"),
).Where(goqu.C("event_name").Eq(utils.GetNetwork() + ":" + string(eventName)))
if lastSentFilter != "" {
diff --git a/backend/pkg/notification/notifications.go b/backend/pkg/notification/notifications.go
index 51cb926ae..76bedf688 100644
--- a/backend/pkg/notification/notifications.go
+++ b/backend/pkg/notification/notifications.go
@@ -492,6 +492,8 @@ func queueNotifications(notificationsByUserID types.NotificationsPerUserId, useD
}
}
}
+
+ // obsolete as notifications are anyway sent on a per-epoch basis
for epoch, subIDs := range subByEpoch {
// update that we've queued the subscription (last sent rather means last queued)
err := db.UpdateSubscriptionsLastSent(subIDs, time.Now(), epoch, useDB)
@@ -520,6 +522,7 @@ func queueNotifications(notificationsByUserID types.NotificationsPerUserId, useD
}
}
+ // no need to batch here as the internal state will become obsolete
for state, subs := range stateToSub {
subArray := make([]int64, 0)
for subID := range subs {
@@ -1549,21 +1552,21 @@ func collectAttestationAndOfflineValidatorNotifications(notificationsByUserID ty
t := hex.EncodeToString(validator.Pubkey)
subs := subMap[t]
for _, sub := range subs {
- if sub.State.String == "" || sub.State.String == "-" { // discard online notifications that do not have a corresponding offline notification
- continue
- }
+ // if sub.State.String == "" || sub.State.String == "-" { // discard online notifications that do not have a corresponding offline notification
+ // continue
+ // }
- originalLastSeenEpoch, err := strconv.ParseUint(sub.State.String, 10, 64)
- if err != nil {
- // I have no idea what just happened.
- return fmt.Errorf("this should never happen. couldn't parse state as uint64: %v", err)
- }
+ // originalLastSeenEpoch, err := strconv.ParseUint(sub.State.String, 10, 64)
+ // if err != nil {
+ // // I have no idea what just happened.
+ // return fmt.Errorf("this should never happen. couldn't parse state as uint64: %v", err)
+ // }
- epochsSinceOffline := epoch - originalLastSeenEpoch
+ // epochsSinceOffline := epoch - originalLastSeenEpoch
- if epochsSinceOffline > epoch { // fix overflow
- epochsSinceOffline = 4
- }
+ // if epochsSinceOffline > epoch { // fix overflow
+ // epochsSinceOffline = 4
+ // }
if sub.UserID == nil || sub.ID == nil {
return fmt.Errorf("error expected userId and subId to be defined but got user: %v, sub: %v", sub.UserID, sub.ID)
@@ -1582,7 +1585,6 @@ func collectAttestationAndOfflineValidatorNotifications(notificationsByUserID ty
},
ValidatorIndex: validator.Index,
IsOffline: false,
- EpochsOffline: epochsSinceOffline,
}
notificationsByUserID.AddNotification(n)
@@ -1597,7 +1599,6 @@ type validatorIsOfflineNotification struct {
types.NotificationBaseImpl
ValidatorIndex uint64
- EpochsOffline uint64
IsOffline bool
}
@@ -1611,9 +1612,9 @@ func (n *validatorIsOfflineNotification) GetInfo(includeUrl bool) string {
}
} else {
if includeUrl {
- return fmt.Sprintf(`Validator %[1]v is back online since epoch %[2]v (was offline for %[4]v epoch(s)).`, n.ValidatorIndex, n.Epoch, utils.Config.Frontend.SiteDomain, n.EpochsOffline)
+ return fmt.Sprintf(`Validator %[1]v is back online since epoch %[2]v.`, n.ValidatorIndex, n.Epoch, utils.Config.Frontend.SiteDomain)
} else {
- return fmt.Sprintf(`Validator %v is back online since epoch %v (was offline for %v epoch(s)).`, n.ValidatorIndex, n.Epoch, n.EpochsOffline)
+ return fmt.Sprintf(`Validator %v is back online since epoch %v.`, n.ValidatorIndex, n.Epoch)
}
}
}
@@ -1630,7 +1631,7 @@ func (n *validatorIsOfflineNotification) GetInfoMarkdown() string {
if n.IsOffline {
return fmt.Sprintf(`Validator [%[1]v](https://%[3]v/validator/%[1]v) is offline since epoch [%[2]v](https://%[3]v/epoch/%[2]v).`, n.ValidatorIndex, n.Epoch, utils.Config.Frontend.SiteDomain)
} else {
- return fmt.Sprintf(`Validator [%[1]v](https://%[3]v/validator/%[1]v) is back online since epoch [%[2]v](https://%[3]v/epoch/%[2]v) (was offline for %[4]v epoch(s)).`, n.ValidatorIndex, n.Epoch, utils.Config.Frontend.SiteDomain, n.EpochsOffline)
+ return fmt.Sprintf(`Validator [%[1]v](https://%[3]v/validator/%[1]v) is back online since epoch [%[2]v](https://%[3]v/epoch/%[2]v).`, n.ValidatorIndex, n.Epoch, utils.Config.Frontend.SiteDomain)
}
}
From 161652041681b02afa609ed8620731b301d1ff58 Mon Sep 17 00:00:00 2001
From: peter <1674920+peterbitfly@users.noreply.github.com>
Date: Mon, 2 Sep 2024 14:46:24 +0200
Subject: [PATCH 16/94] fix(notifications): properly log error
---
backend/pkg/notification/notifications.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/backend/pkg/notification/notifications.go b/backend/pkg/notification/notifications.go
index 76bedf688..ba06f4132 100644
--- a/backend/pkg/notification/notifications.go
+++ b/backend/pkg/notification/notifications.go
@@ -166,7 +166,7 @@ func notificationSender() {
if err != nil {
log.Error(err, "error getting advisory lock from db", 0)
- conn.Close()
+ err := conn.Close()
if err != nil {
log.Error(err, "error returning connection to connection pool", 0)
}
From bc57bfd680e69bd97c75a239b659f35ca510f80b Mon Sep 17 00:00:00 2001
From: peter <1674920+peterbitfly@users.noreply.github.com>
Date: Mon, 9 Sep 2024 09:54:53 +0000
Subject: [PATCH 17/94] simplify extraction of user ids from the notification
map
---
backend/pkg/notification/notifications.go | 63 +++++++++++------------
1 file changed, 30 insertions(+), 33 deletions(-)
diff --git a/backend/pkg/notification/notifications.go b/backend/pkg/notification/notifications.go
index e68f5f3f6..71ec9e0eb 100644
--- a/backend/pkg/notification/notifications.go
+++ b/backend/pkg/notification/notifications.go
@@ -8,6 +8,8 @@ import (
"encoding/hex"
"encoding/json"
"errors"
+ "maps"
+ "slices"
"fmt"
"html/template"
@@ -283,28 +285,28 @@ func collectNotifications(epoch uint64) (types.NotificationsPerUserId, error) {
return nil, fmt.Errorf("error getting dashboard definitions: %v", err)
}
- // Now initialize the validator dashboard configuration map
- validatorDashboardConfig := &types.ValidatorDashboardConfig{
- DashboardsByUserId: make(map[types.UserId]map[types.DashboardId]*types.ValidatorDashboard),
- }
- for _, row := range dashboardDefinitions {
- if validatorDashboardConfig.DashboardsByUserId[row.UserId] == nil {
- validatorDashboardConfig.DashboardsByUserId[row.UserId] = make(map[types.DashboardId]*types.ValidatorDashboard)
- }
- if validatorDashboardConfig.DashboardsByUserId[row.UserId][row.DashboardId] == nil {
- validatorDashboardConfig.DashboardsByUserId[row.UserId][row.DashboardId] = &types.ValidatorDashboard{
- Name: row.DashboardName,
- Groups: make(map[types.DashboardGroupId]*types.ValidatorDashboardGroup),
- }
- }
- if validatorDashboardConfig.DashboardsByUserId[row.UserId][row.DashboardId].Groups[row.GroupId] == nil {
- validatorDashboardConfig.DashboardsByUserId[row.UserId][row.DashboardId].Groups[row.GroupId] = &types.ValidatorDashboardGroup{
- Name: row.GroupName,
- Validators: []uint64{},
- }
- }
- validatorDashboardConfig.DashboardsByUserId[row.UserId][row.DashboardId].Groups[row.GroupId].Validators = append(validatorDashboardConfig.DashboardsByUserId[row.UserId][row.DashboardId].Groups[row.GroupId].Validators, uint64(row.ValidatorIndex))
- }
+ // // Now initialize the validator dashboard configuration map
+ // validatorDashboardConfig := &types.ValidatorDashboardConfig{
+ // DashboardsByUserId: make(map[types.UserId]map[types.DashboardId]*types.ValidatorDashboard),
+ // }
+ // for _, row := range dashboardDefinitions {
+ // if validatorDashboardConfig.DashboardsByUserId[row.UserId] == nil {
+ // validatorDashboardConfig.DashboardsByUserId[row.UserId] = make(map[types.DashboardId]*types.ValidatorDashboard)
+ // }
+ // if validatorDashboardConfig.DashboardsByUserId[row.UserId][row.DashboardId] == nil {
+ // validatorDashboardConfig.DashboardsByUserId[row.UserId][row.DashboardId] = &types.ValidatorDashboard{
+ // Name: row.DashboardName,
+ // Groups: make(map[types.DashboardGroupId]*types.ValidatorDashboardGroup),
+ // }
+ // }
+ // if validatorDashboardConfig.DashboardsByUserId[row.UserId][row.DashboardId].Groups[row.GroupId] == nil {
+ // validatorDashboardConfig.DashboardsByUserId[row.UserId][row.DashboardId].Groups[row.GroupId] = &types.ValidatorDashboardGroup{
+ // Name: row.GroupName,
+ // Validators: []uint64{},
+ // }
+ // }
+ // validatorDashboardConfig.DashboardsByUserId[row.UserId][row.DashboardId].Groups[row.GroupId].Validators = append(validatorDashboardConfig.DashboardsByUserId[row.UserId][row.DashboardId].Groups[row.GroupId].Validators, uint64(row.ValidatorIndex))
+ // }
// TODO: pass the validatorDashboardConfig to the notification collection functions
// The following functions will collect the notifications and add them to the
@@ -526,7 +528,7 @@ func queueNotifications(notificationsByUserID types.NotificationsPerUserId, useD
for state, subs := range stateToSub {
subArray := make([]int64, 0)
for subID := range subs {
- subArray = append(subArray, int64(subID))
+ subArray = append(subArray, int64(subID)) //nolint:gosec
}
_, err := db.FrontendWriterDB.Exec(`UPDATE users_subscriptions SET internal_state = $1 WHERE id = ANY($2)`, state, pq.Int64Array(subArray))
if err != nil {
@@ -582,10 +584,7 @@ func getNetwork() string {
}
func queuePushNotification(notificationsByUserID types.NotificationsPerUserId, useDB *sqlx.DB) error {
- userIDs := []types.UserId{}
- for userID := range notificationsByUserID {
- userIDs = append(userIDs, userID)
- }
+ userIDs := slices.Collect(maps.Keys(notificationsByUserID))
tokensByUserID, err := GetUserPushTokenByIds(userIDs)
if err != nil {
@@ -690,10 +689,8 @@ func sendPushNotifications(useDB *sqlx.DB) error {
}
func queueEmailNotifications(notificationsByUserID types.NotificationsPerUserId, useDB *sqlx.DB) error {
- userIDs := []types.UserId{}
- for userID := range notificationsByUserID {
- userIDs = append(userIDs, userID)
- }
+ userIDs := slices.Collect(maps.Keys(notificationsByUserID))
+
emailsByUserID, err := GetUserEmailsByIds(userIDs)
if err != nil {
metrics.Errors.WithLabelValues("notifications_get_user_mail_by_id").Inc()
@@ -1009,7 +1006,7 @@ func sendWebhookNotifications(useDB *sqlx.DB) error {
go func(n types.TransitWebhook) {
if n.Content.Webhook.Retries > 0 {
- time.Sleep(time.Duration(n.Content.Webhook.Retries) * time.Second)
+ time.Sleep(time.Duration(n.Content.Webhook.Retries) * time.Second) //nolint:gosec
}
resp, err := client.Post(n.Content.Webhook.Url, "application/json", reqBody)
if err != nil {
@@ -1127,7 +1124,7 @@ func sendDiscordNotifications(useDB *sqlx.DB) error {
break // stop
}
// sleep between retries
- time.Sleep(time.Duration(webhook.Retries) * time.Second)
+ time.Sleep(time.Duration(webhook.Retries) * time.Second) //nolint:gosec
reqBody := new(bytes.Buffer)
err := json.NewEncoder(reqBody).Encode(reqs[i].Content.DiscordRequest)
From 3a148a7b108216aa7abf63aee42c5f84e62821bc Mon Sep 17 00:00:00 2001
From: peter <1674920+peterbitfly@users.noreply.github.com>
Date: Mon, 9 Sep 2024 09:58:32 +0000
Subject: [PATCH 18/94] chore(notifications): please linter
---
backend/pkg/notification/notifications.go | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/backend/pkg/notification/notifications.go b/backend/pkg/notification/notifications.go
index 71ec9e0eb..4b5512f86 100644
--- a/backend/pkg/notification/notifications.go
+++ b/backend/pkg/notification/notifications.go
@@ -528,7 +528,7 @@ func queueNotifications(notificationsByUserID types.NotificationsPerUserId, useD
for state, subs := range stateToSub {
subArray := make([]int64, 0)
for subID := range subs {
- subArray = append(subArray, int64(subID)) //nolint:gosec
+ subArray = append(subArray, int64(subID))
}
_, err := db.FrontendWriterDB.Exec(`UPDATE users_subscriptions SET internal_state = $1 WHERE id = ANY($2)`, state, pq.Int64Array(subArray))
if err != nil {
@@ -1006,7 +1006,7 @@ func sendWebhookNotifications(useDB *sqlx.DB) error {
go func(n types.TransitWebhook) {
if n.Content.Webhook.Retries > 0 {
- time.Sleep(time.Duration(n.Content.Webhook.Retries) * time.Second) //nolint:gosec
+ time.Sleep(time.Duration(n.Content.Webhook.Retries) * time.Second)
}
resp, err := client.Post(n.Content.Webhook.Url, "application/json", reqBody)
if err != nil {
@@ -1124,7 +1124,7 @@ func sendDiscordNotifications(useDB *sqlx.DB) error {
break // stop
}
// sleep between retries
- time.Sleep(time.Duration(webhook.Retries) * time.Second) //nolint:gosec
+ time.Sleep(time.Duration(webhook.Retries) * time.Second)
reqBody := new(bytes.Buffer)
err := json.NewEncoder(reqBody).Encode(reqs[i].Content.DiscordRequest)
From f8265c5851a59abf5a0ff668c08e522e4b4fcfd1 Mon Sep 17 00:00:00 2001
From: peter <1674920+peterbitfly@users.noreply.github.com>
Date: Mon, 9 Sep 2024 09:59:24 +0000
Subject: [PATCH 19/94] chore(notifications): disable loading dashboard
configurations
---
backend/pkg/notification/notifications.go | 42 +++++++++++------------
1 file changed, 21 insertions(+), 21 deletions(-)
diff --git a/backend/pkg/notification/notifications.go b/backend/pkg/notification/notifications.go
index 4b5512f86..59399f3b7 100644
--- a/backend/pkg/notification/notifications.go
+++ b/backend/pkg/notification/notifications.go
@@ -263,27 +263,27 @@ func collectNotifications(epoch uint64) (types.NotificationsPerUserId, error) {
ValidatorIndex types.ValidatorIndex `db:"validator_index"`
}
- log.Infof("retrieving dashboard definitions")
- // Retrieve all dashboard definitions to be able to retrieve validators included in
- // the group notification subscriptions
- // TODO: add a filter to retrieve only groups that have notifications enabled
- // Needs a new field in the db
- var dashboardDefinitions []dashboardDefinitionRow
- err = db.AlloyWriter.Select(&dashboardDefinitions, `
- select
- users_val_dashboards.id as dashboard_id,
- users_val_dashboards.name as dashboard_name,
- users_val_dashboards.user_id,
- users_val_dashboards_groups.id as group_id,
- users_val_dashboards_groups.name as group_name,
- users_val_dashboards_validators.validator_index
- from users_val_dashboards
- left join users_val_dashboards_groups on users_val_dashboards_groups.dashboard_id = users_val_dashboards.id
- left join users_val_dashboards_validators on users_val_dashboards_validators.dashboard_id = users_val_dashboards_groups.dashboard_id AND users_val_dashboards_validators.group_id = users_val_dashboards_groups.id;
- `)
- if err != nil {
- return nil, fmt.Errorf("error getting dashboard definitions: %v", err)
- }
+ // log.Infof("retrieving dashboard definitions")
+ // // Retrieve all dashboard definitions to be able to retrieve validators included in
+ // // the group notification subscriptions
+ // // TODO: add a filter to retrieve only groups that have notifications enabled
+ // // Needs a new field in the db
+ // var dashboardDefinitions []dashboardDefinitionRow
+ // err = db.AlloyWriter.Select(&dashboardDefinitions, `
+ // select
+ // users_val_dashboards.id as dashboard_id,
+ // users_val_dashboards.name as dashboard_name,
+ // users_val_dashboards.user_id,
+ // users_val_dashboards_groups.id as group_id,
+ // users_val_dashboards_groups.name as group_name,
+ // users_val_dashboards_validators.validator_index
+ // from users_val_dashboards
+ // left join users_val_dashboards_groups on users_val_dashboards_groups.dashboard_id = users_val_dashboards.id
+ // left join users_val_dashboards_validators on users_val_dashboards_validators.dashboard_id = users_val_dashboards_groups.dashboard_id AND users_val_dashboards_validators.group_id = users_val_dashboards_groups.id;
+ // `)
+ // if err != nil {
+ // return nil, fmt.Errorf("error getting dashboard definitions: %v", err)
+ // }
// // Now initialize the validator dashboard configuration map
// validatorDashboardConfig := &types.ValidatorDashboardConfig{
From 23ed6e285f69603811e1085f7bfaa7485428f2aa Mon Sep 17 00:00:00 2001
From: peter <1674920+peterbitfly@users.noreply.github.com>
Date: Mon, 9 Sep 2024 09:59:46 +0000
Subject: [PATCH 20/94] chore(notifications): remove unused code for dashboard
configurations
---
backend/pkg/notification/notifications.go | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/backend/pkg/notification/notifications.go b/backend/pkg/notification/notifications.go
index 59399f3b7..a8a6209eb 100644
--- a/backend/pkg/notification/notifications.go
+++ b/backend/pkg/notification/notifications.go
@@ -254,14 +254,14 @@ func collectNotifications(epoch uint64) (types.NotificationsPerUserId, error) {
log.Infof("started collecting notifications")
- type dashboardDefinitionRow struct {
- DashboardId types.DashboardId `db:"dashboard_id"`
- DashboardName string `db:"dashboard_name"`
- UserId types.UserId `db:"user_id"`
- GroupId types.DashboardGroupId `db:"group_id"`
- GroupName string `db:"group_name"`
- ValidatorIndex types.ValidatorIndex `db:"validator_index"`
- }
+ // type dashboardDefinitionRow struct {
+ // DashboardId types.DashboardId `db:"dashboard_id"`
+ // DashboardName string `db:"dashboard_name"`
+ // UserId types.UserId `db:"user_id"`
+ // GroupId types.DashboardGroupId `db:"group_id"`
+ // GroupName string `db:"group_name"`
+ // ValidatorIndex types.ValidatorIndex `db:"validator_index"`
+ // }
// log.Infof("retrieving dashboard definitions")
// // Retrieve all dashboard definitions to be able to retrieve validators included in
From c8d0e0f993ebc902d9d433c1a9f7e7c68feb83be Mon Sep 17 00:00:00 2001
From: peter <1674920+peterbitfly@users.noreply.github.com>
Date: Mon, 9 Sep 2024 12:14:42 +0000
Subject: [PATCH 21/94] feat(notifications): store queued notifications in
network db
---
backend/pkg/commons/db/subscriptions.go | 4 +-
backend/pkg/notification/notifications.go | 89 +++++++++++------------
2 files changed, 46 insertions(+), 47 deletions(-)
diff --git a/backend/pkg/commons/db/subscriptions.go b/backend/pkg/commons/db/subscriptions.go
index 3edbc3d5f..639f70c8b 100644
--- a/backend/pkg/commons/db/subscriptions.go
+++ b/backend/pkg/commons/db/subscriptions.go
@@ -261,8 +261,8 @@ func GetSubscriptions(filter GetSubscriptionsFilter) ([]*types.Subscription, err
}
// UpdateSubscriptionsLastSent updates `last_sent_ts` column of the `users_subscriptions` table.
-func UpdateSubscriptionsLastSent(subscriptionIDs []uint64, sent time.Time, epoch uint64, useDB *sqlx.DB) error {
- _, err := useDB.Exec(`
+func UpdateSubscriptionsLastSent(subscriptionIDs []uint64, sent time.Time, epoch uint64) error {
+ _, err := FrontendWriterDB.Exec(`
UPDATE users_subscriptions
SET last_sent_ts = TO_TIMESTAMP($1), last_sent_epoch = $2
WHERE id = ANY($3)`, sent.Unix(), epoch, pq.Array(subscriptionIDs))
diff --git a/backend/pkg/notification/notifications.go b/backend/pkg/notification/notifications.go
index a8a6209eb..7b148d0d5 100644
--- a/backend/pkg/notification/notifications.go
+++ b/backend/pkg/notification/notifications.go
@@ -34,7 +34,6 @@ import (
"github.com/gobitfly/beaconchain/pkg/commons/types"
"github.com/gobitfly/beaconchain/pkg/commons/utils"
- "github.com/jmoiron/sqlx"
"github.com/lib/pq"
"github.com/rocket-pool/rocketpool-go/utils/eth"
"golang.org/x/text/cases"
@@ -126,7 +125,7 @@ func notificationCollector() {
break
}
- queueNotifications(notifications, db.FrontendWriterDB) // this caused the collected notifications to be queued and sent
+ queueNotifications(notifications) // this caused the collected notifications to be queued and sent
// Network DB Notifications (user related, must only run on one instance ever!!!!)
if utils.Config.Notifications.UserDBNotifications {
@@ -139,7 +138,7 @@ func notificationCollector() {
continue
}
- queueNotifications(userNotifications, db.FrontendWriterDB)
+ queueNotifications(userNotifications)
}
log.InfoWithFields(log.Fields{"notifications": len(notifications), "duration": time.Since(start), "epoch": epoch}, "notifications completed")
@@ -177,12 +176,12 @@ func notificationSender() {
}
log.Infof("lock obtained")
- err = dispatchNotifications(db.FrontendWriterDB)
+ err = dispatchNotifications()
if err != nil {
log.Error(err, "error dispatching notifications", 0)
}
- err = garbageCollectNotificationQueue(db.FrontendWriterDB)
+ err = garbageCollectNotificationQueue()
if err != nil {
log.Error(err, "error garbage collecting notification queue", 0)
}
@@ -464,20 +463,20 @@ func collectUserDbNotifications(epoch uint64) (types.NotificationsPerUserId, err
return notificationsByUserID, nil
}
-func queueNotifications(notificationsByUserID types.NotificationsPerUserId, useDB *sqlx.DB) {
+func queueNotifications(notificationsByUserID types.NotificationsPerUserId) {
subByEpoch := map[uint64][]uint64{}
- err := queueEmailNotifications(notificationsByUserID, useDB)
+ err := queueEmailNotifications(notificationsByUserID)
if err != nil {
log.Error(err, "error queuing email notifications", 0)
}
- err = queuePushNotification(notificationsByUserID, useDB)
+ err = queuePushNotification(notificationsByUserID)
if err != nil {
log.Error(err, "error queuing push notifications", 0)
}
- err = queueWebhookNotifications(notificationsByUserID, useDB)
+ err = queueWebhookNotifications(notificationsByUserID)
if err != nil {
log.Error(err, "error queuing webhook notifications", 0)
}
@@ -498,7 +497,7 @@ func queueNotifications(notificationsByUserID types.NotificationsPerUserId, useD
// obsolete as notifications are anyway sent on a per-epoch basis
for epoch, subIDs := range subByEpoch {
// update that we've queued the subscription (last sent rather means last queued)
- err := db.UpdateSubscriptionsLastSent(subIDs, time.Now(), epoch, useDB)
+ err := db.UpdateSubscriptionsLastSent(subIDs, time.Now(), epoch)
if err != nil {
log.Error(err, "error updating sent-time of sent notifications", 0)
metrics.Errors.WithLabelValues("notifications_updating_sent_time").Inc()
@@ -537,23 +536,23 @@ func queueNotifications(notificationsByUserID types.NotificationsPerUserId, useD
}
}
-func dispatchNotifications(useDB *sqlx.DB) error {
- err := sendEmailNotifications(useDB)
+func dispatchNotifications() error {
+ err := sendEmailNotifications()
if err != nil {
return fmt.Errorf("error sending email notifications, err: %w", err)
}
- err = sendPushNotifications(useDB)
+ err = sendPushNotifications()
if err != nil {
return fmt.Errorf("error sending push notifications, err: %w", err)
}
- err = sendWebhookNotifications(useDB)
+ err = sendWebhookNotifications()
if err != nil {
return fmt.Errorf("error sending webhook notifications, err: %w", err)
}
- err = sendDiscordNotifications(useDB)
+ err = sendDiscordNotifications()
if err != nil {
return fmt.Errorf("error sending webhook discord notifications, err: %w", err)
}
@@ -562,8 +561,8 @@ func dispatchNotifications(useDB *sqlx.DB) error {
}
// garbageCollectNotificationQueue deletes entries from the notification queue that have been processed
-func garbageCollectNotificationQueue(useDB *sqlx.DB) error {
- rows, err := useDB.Exec(`DELETE FROM notification_queue WHERE (sent < now() - INTERVAL '30 minutes') OR (created < now() - INTERVAL '1 hour')`)
+func garbageCollectNotificationQueue() error {
+ rows, err := db.WriterDb.Exec(`DELETE FROM notification_queue WHERE (sent < now() - INTERVAL '30 minutes') OR (created < now() - INTERVAL '1 hour')`)
if err != nil {
return fmt.Errorf("error deleting from notification_queue %w", err)
}
@@ -583,7 +582,7 @@ func getNetwork() string {
return ""
}
-func queuePushNotification(notificationsByUserID types.NotificationsPerUserId, useDB *sqlx.DB) error {
+func queuePushNotification(notificationsByUserID types.NotificationsPerUserId) error {
userIDs := slices.Collect(maps.Keys(notificationsByUserID))
tokensByUserID, err := GetUserPushTokenByIds(userIDs)
@@ -636,7 +635,7 @@ func queuePushNotification(notificationsByUserID types.NotificationsPerUserId, u
Messages: batch,
}
- _, err = useDB.Exec(`INSERT INTO notification_queue (created, channel, content) VALUES ($1, 'push', $2)`, time.Now(), transitPushContent)
+ _, err = db.WriterDb.Exec(`INSERT INTO notification_queue (created, channel, content) VALUES ($1, 'push', $2)`, time.Now(), transitPushContent)
if err != nil {
log.Error(err, "error writing transit push notification to db", 0)
return
@@ -646,10 +645,10 @@ func queuePushNotification(notificationsByUserID types.NotificationsPerUserId, u
return nil
}
-func sendPushNotifications(useDB *sqlx.DB) error {
+func sendPushNotifications() error {
var notificationQueueItem []types.TransitPush
- err := useDB.Select(¬ificationQueueItem, `SELECT
+ err := db.WriterDb.Select(¬ificationQueueItem, `SELECT
id,
created,
sent,
@@ -679,7 +678,7 @@ func sendPushNotifications(useDB *sqlx.DB) error {
metrics.NotificationsSent.WithLabelValues("push", "200").Add(float64(len(n.Content.Messages)))
}
- _, err = useDB.Exec(`UPDATE notification_queue SET sent = now() WHERE id = $1`, n.Id)
+ _, err = db.WriterDb.Exec(`UPDATE notification_queue SET sent = now() WHERE id = $1`, n.Id)
if err != nil {
return fmt.Errorf("error updating sent status for push notification with id: %v, err: %w", n.Id, err)
}
@@ -688,7 +687,7 @@ func sendPushNotifications(useDB *sqlx.DB) error {
return nil
}
-func queueEmailNotifications(notificationsByUserID types.NotificationsPerUserId, useDB *sqlx.DB) error {
+func queueEmailNotifications(notificationsByUserID types.NotificationsPerUserId) error {
userIDs := slices.Collect(maps.Keys(notificationsByUserID))
emailsByUserID, err := GetUserEmailsByIds(userIDs)
@@ -777,7 +776,7 @@ func queueEmailNotifications(notificationsByUserID types.NotificationsPerUserId,
Attachments: attachments,
}
- _, err = useDB.Exec(`INSERT INTO notification_queue (created, channel, content) VALUES ($1, 'email', $2)`, time.Now(), transitEmailContent)
+ _, err = db.WriterDb.Exec(`INSERT INTO notification_queue (created, channel, content) VALUES ($1, 'email', $2)`, time.Now(), transitEmailContent)
if err != nil {
log.Error(err, "error writing transit email to db", 0)
}
@@ -786,10 +785,10 @@ func queueEmailNotifications(notificationsByUserID types.NotificationsPerUserId,
return nil
}
-func sendEmailNotifications(useDb *sqlx.DB) error {
+func sendEmailNotifications() error {
var notificationQueueItem []types.TransitEmail
- err := useDb.Select(¬ificationQueueItem, `SELECT
+ err := db.WriterDb.Select(¬ificationQueueItem, `SELECT
id,
created,
sent,
@@ -812,7 +811,7 @@ func sendEmailNotifications(useDb *sqlx.DB) error {
metrics.NotificationsSent.WithLabelValues("email", "200").Inc()
}
}
- _, err = useDb.Exec(`UPDATE notification_queue set sent = now() where id = $1`, n.Id)
+ _, err = db.WriterDb.Exec(`UPDATE notification_queue set sent = now() where id = $1`, n.Id)
if err != nil {
return fmt.Errorf("error updating sent status for email notification with id: %v, err: %w", n.Id, err)
}
@@ -820,10 +819,10 @@ func sendEmailNotifications(useDb *sqlx.DB) error {
return nil
}
-func queueWebhookNotifications(notificationsByUserID types.NotificationsPerUserId, useDB *sqlx.DB) error {
+func queueWebhookNotifications(notificationsByUserID types.NotificationsPerUserId) error {
for userID, userNotifications := range notificationsByUserID {
var webhooks []types.UserWebhook
- err := useDB.Select(&webhooks, `
+ err := db.FrontendWriterDB.Select(&webhooks, `
SELECT
id,
user_id,
@@ -861,7 +860,7 @@ func queueWebhookNotifications(notificationsByUserID types.NotificationsPerUserI
if len(notifications) > 0 {
// reset Retries
if w.Retries > 5 && w.LastSent.Valid && w.LastSent.Time.Add(time.Hour).Before(time.Now()) {
- _, err = useDB.Exec(`UPDATE users_webhooks SET retries = 0 WHERE id = $1;`, w.ID)
+ _, err = db.FrontendWriterDB.Exec(`UPDATE users_webhooks SET retries = 0 WHERE id = $1;`, w.ID)
if err != nil {
log.Error(err, "error updating users_webhooks table; setting retries to zero", 0)
continue
@@ -938,7 +937,7 @@ func queueWebhookNotifications(notificationsByUserID types.NotificationsPerUserI
}
// process notifs
for _, n := range notifs {
- _, err = useDB.Exec(`INSERT INTO notification_queue (created, channel, content) VALUES (now(), $1, $2);`, n.Channel, n.Content)
+ _, err = db.WriterDb.Exec(`INSERT INTO notification_queue (created, channel, content) VALUES (now(), $1, $2);`, n.Channel, n.Content)
if err != nil {
log.Error(err, "error inserting into webhooks_queue", 0)
} else {
@@ -948,7 +947,7 @@ func queueWebhookNotifications(notificationsByUserID types.NotificationsPerUserI
// process discord notifs
for _, dNotifs := range discordNotifMap {
for _, n := range dNotifs {
- _, err = useDB.Exec(`INSERT INTO notification_queue (created, channel, content) VALUES (now(), 'webhook_discord', $1);`, n)
+ _, err = db.WriterDb.Exec(`INSERT INTO notification_queue (created, channel, content) VALUES (now(), 'webhook_discord', $1);`, n)
if err != nil {
log.Error(err, "error inserting into webhooks_queue (discord)", 0)
continue
@@ -961,10 +960,10 @@ func queueWebhookNotifications(notificationsByUserID types.NotificationsPerUserI
return nil
}
-func sendWebhookNotifications(useDB *sqlx.DB) error {
+func sendWebhookNotifications() error {
var notificationQueueItem []types.TransitWebhook
- err := useDB.Select(¬ificationQueueItem, `SELECT
+ err := db.WriterDb.Select(¬ificationQueueItem, `SELECT
id,
created,
sent,
@@ -981,7 +980,7 @@ func sendWebhookNotifications(useDB *sqlx.DB) error {
for _, n := range notificationQueueItem {
// do not retry after 5 attempts
if n.Content.Webhook.Retries > 5 {
- _, err := db.FrontendWriterDB.Exec(`DELETE FROM notification_queue WHERE id = $1`, n.Id)
+ _, err := db.WriterDb.Exec(`DELETE FROM notification_queue WHERE id = $1`, n.Id)
if err != nil {
return fmt.Errorf("error deleting from notification queue: %w", err)
}
@@ -997,7 +996,7 @@ func sendWebhookNotifications(useDB *sqlx.DB) error {
_, err = url.Parse(n.Content.Webhook.Url)
if err != nil {
- _, err := db.FrontendWriterDB.Exec(`DELETE FROM notification_queue WHERE id = $1`, n.Id)
+ _, err := db.WriterDb.Exec(`DELETE FROM notification_queue WHERE id = $1`, n.Id)
if err != nil {
return fmt.Errorf("error deleting from notification queue: %w", err)
}
@@ -1017,14 +1016,14 @@ func sendWebhookNotifications(useDB *sqlx.DB) error {
}
defer resp.Body.Close()
- _, err = useDB.Exec(`UPDATE notification_queue SET sent = now() WHERE id = $1`, n.Id)
+ _, err = db.WriterDb.Exec(`UPDATE notification_queue SET sent = now() WHERE id = $1`, n.Id)
if err != nil {
log.Error(err, "error updating notification_queue table", 0)
return
}
if resp != nil && resp.StatusCode < 400 {
- _, err = useDB.Exec(`UPDATE users_webhooks SET retries = 0, last_sent = now() WHERE id = $1;`, n.Content.Webhook.ID)
+ _, err = db.FrontendWriterDB.Exec(`UPDATE users_webhooks SET retries = 0, last_sent = now() WHERE id = $1;`, n.Content.Webhook.ID)
if err != nil {
log.Error(err, "error updating users_webhooks table", 0)
return
@@ -1042,7 +1041,7 @@ func sendWebhookNotifications(useDB *sqlx.DB) error {
errResp.Body = string(b)
}
- _, err = useDB.Exec(`UPDATE users_webhooks SET retries = retries + 1, last_sent = now(), request = $2, response = $3 WHERE id = $1;`, n.Content.Webhook.ID, n.Content, errResp)
+ _, err = db.FrontendWriterDB.Exec(`UPDATE users_webhooks SET retries = retries + 1, last_sent = now(), request = $2, response = $3 WHERE id = $1;`, n.Content.Webhook.ID, n.Content, errResp)
if err != nil {
log.Error(err, "error updating users_webhooks table", 0)
return
@@ -1053,10 +1052,10 @@ func sendWebhookNotifications(useDB *sqlx.DB) error {
return nil
}
-func sendDiscordNotifications(useDB *sqlx.DB) error {
+func sendDiscordNotifications() error {
var notificationQueueItem []types.TransitDiscord
- err := useDB.Select(¬ificationQueueItem, `SELECT
+ err := db.WriterDb.Select(¬ificationQueueItem, `SELECT
id,
created,
sent,
@@ -1077,7 +1076,7 @@ func sendDiscordNotifications(useDB *sqlx.DB) error {
for _, n := range notificationQueueItem {
// purge the event from existence if the retry counter is over 5
if n.Content.Webhook.Retries > 5 {
- _, err = db.FrontendWriterDB.Exec(`DELETE FROM notification_queue where id = $1`, n.Id)
+ _, err = db.WriterDb.Exec(`DELETE FROM notification_queue where id = $1`, n.Id)
if err != nil {
log.Warnf("failed to delete notification from queue: %v", err)
}
@@ -1097,7 +1096,7 @@ func sendDiscordNotifications(useDB *sqlx.DB) error {
go func(webhook types.UserWebhook, reqs []types.TransitDiscord) {
defer func() {
// update retries counters in db based on end result
- _, err = useDB.Exec(`UPDATE users_webhooks SET retries = $1, last_sent = now() WHERE id = $2;`, webhook.Retries, webhook.ID)
+ _, err = db.FrontendWriterDB.Exec(`UPDATE users_webhooks SET retries = $1, last_sent = now() WHERE id = $2;`, webhook.Retries, webhook.ID)
if err != nil {
log.Warnf("failed to update retries counter to %v for webhook %v: %v", webhook.Retries, webhook.ID, err)
}
@@ -1107,7 +1106,7 @@ func sendDiscordNotifications(useDB *sqlx.DB) error {
for _, req := range reqs {
ids = append(ids, req.Id)
}
- _, err = db.FrontendWriterDB.Exec(`UPDATE notification_queue SET sent = now() where id = ANY($1)`, pq.Array(ids))
+ _, err = db.WriterDb.Exec(`UPDATE notification_queue SET sent = now() where id = ANY($1)`, pq.Array(ids))
if err != nil {
log.Warnf("failed to update sent for notifcations in queue: %v", err)
}
@@ -1161,7 +1160,7 @@ func sendDiscordNotifications(useDB *sqlx.DB) error {
} else {
log.Error(nil, "error pushing discord webhook", 0, map[string]interface{}{"errResp.Body": errResp.Body, "webhook.Url": webhook.Url})
}
- _, err = useDB.Exec(`UPDATE users_webhooks SET request = $2, response = $3 WHERE id = $1;`, webhook.ID, reqs[i].Content.DiscordRequest, errResp)
+ _, err = db.FrontendWriterDB.Exec(`UPDATE users_webhooks SET request = $2, response = $3 WHERE id = $1;`, webhook.ID, reqs[i].Content.DiscordRequest, errResp)
if err != nil {
log.Error(err, "error storing failure data in users_webhooks table", 0)
}
From 86da74c466e8e61e90e1b3e1bf7cc7744cc2963a Mon Sep 17 00:00:00 2001
From: peter <1674920+peterbitfly@users.noreply.github.com>
Date: Tue, 10 Sep 2024 10:27:46 +0000
Subject: [PATCH 22/94] fix(notifications): expand args in sql query
---
backend/pkg/notification/db.go | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/backend/pkg/notification/db.go b/backend/pkg/notification/db.go
index 4363c12f7..6e19acf3b 100644
--- a/backend/pkg/notification/db.go
+++ b/backend/pkg/notification/db.go
@@ -58,7 +58,7 @@ func GetSubsForEventFilter(eventName types.EventName, lastSentFilter string, las
}
subMap := make(map[string][]types.Subscription, 0)
- err = db.FrontendWriterDB.Select(&subs, query, args)
+ err = db.FrontendWriterDB.Select(&subs, query, args...)
if err != nil {
return nil, err
}
@@ -69,6 +69,7 @@ func GetSubsForEventFilter(eventName types.EventName, lastSentFilter string, las
}
subMap[sub.EventFilter] = append(subMap[sub.EventFilter], sub)
}
+
return subMap, nil
}
From 454534a2df918840e113c46b120e8f38a8e6c9d0 Mon Sep 17 00:00:00 2001
From: peter <1674920+peterbitfly@users.noreply.github.com>
Date: Tue, 10 Sep 2024 11:09:27 +0000
Subject: [PATCH 23/94] chore(notifications): add some util funcs
---
backend/cmd/misc/main.go | 60 ++++++++++++++---------
backend/pkg/commons/types/frontend.go | 4 +-
backend/pkg/notification/db.go | 6 ++-
backend/pkg/notification/notifications.go | 16 ++++--
4 files changed, 57 insertions(+), 29 deletions(-)
diff --git a/backend/cmd/misc/main.go b/backend/cmd/misc/main.go
index d7b9b77a1..8af9a3499 100644
--- a/backend/cmd/misc/main.go
+++ b/backend/cmd/misc/main.go
@@ -32,6 +32,7 @@ import (
edb "github.com/gobitfly/beaconchain/pkg/exporter/db"
"github.com/gobitfly/beaconchain/pkg/exporter/modules"
"github.com/gobitfly/beaconchain/pkg/exporter/services"
+ "github.com/gobitfly/beaconchain/pkg/notification"
_ "github.com/jackc/pgx/v5/stdlib"
"github.com/pkg/errors"
utilMath "github.com/protolambda/zrnt/eth2/util/math"
@@ -75,7 +76,7 @@ func Run() {
}
configPath := fs.String("config", "config/default.config.yml", "Path to the config file")
- fs.StringVar(&opts.Command, "command", "", "command to run, available: updateAPIKey, applyDbSchema, initBigtableSchema, epoch-export, debug-rewards, debug-blocks, clear-bigtable, index-old-eth1-blocks, update-aggregation-bits, historic-prices-export, index-missing-blocks, export-epoch-missed-slots, migrate-last-attestation-slot-bigtable, export-genesis-validators, update-block-finalization-sequentially, nameValidatorsByRanges, export-stats-totals, export-sync-committee-periods, export-sync-committee-validator-stats, partition-validator-stats, migrate-app-purchases")
+ fs.StringVar(&opts.Command, "command", "", "command to run, available: updateAPIKey, applyDbSchema, initBigtableSchema, epoch-export, debug-rewards, debug-blocks, clear-bigtable, index-old-eth1-blocks, update-aggregation-bits, historic-prices-export, index-missing-blocks, export-epoch-missed-slots, migrate-last-attestation-slot-bigtable, export-genesis-validators, update-block-finalization-sequentially, nameValidatorsByRanges, export-stats-totals, export-sync-committee-periods, export-sync-committee-validator-stats, partition-validator-stats, migrate-app-purchases, collect-notifications")
fs.Uint64Var(&opts.StartEpoch, "start-epoch", 0, "start epoch")
fs.Uint64Var(&opts.EndEpoch, "end-epoch", 0, "end epoch")
fs.Uint64Var(&opts.User, "user", 0, "user id")
@@ -181,27 +182,27 @@ func Run() {
defer db.FrontendWriterDB.Close()
// clickhouse
- db.ClickHouseWriter, db.ClickHouseReader = db.MustInitDB(&types.DatabaseConfig{
- Username: cfg.ClickHouse.WriterDatabase.Username,
- Password: cfg.ClickHouse.WriterDatabase.Password,
- Name: cfg.ClickHouse.WriterDatabase.Name,
- Host: cfg.ClickHouse.WriterDatabase.Host,
- Port: cfg.ClickHouse.WriterDatabase.Port,
- MaxOpenConns: cfg.ClickHouse.WriterDatabase.MaxOpenConns,
- SSL: true,
- MaxIdleConns: cfg.ClickHouse.WriterDatabase.MaxIdleConns,
- }, &types.DatabaseConfig{
- Username: cfg.ClickHouse.ReaderDatabase.Username,
- Password: cfg.ClickHouse.ReaderDatabase.Password,
- Name: cfg.ClickHouse.ReaderDatabase.Name,
- Host: cfg.ClickHouse.ReaderDatabase.Host,
- Port: cfg.ClickHouse.ReaderDatabase.Port,
- MaxOpenConns: cfg.ClickHouse.ReaderDatabase.MaxOpenConns,
- SSL: true,
- MaxIdleConns: cfg.ClickHouse.ReaderDatabase.MaxIdleConns,
- }, "clickhouse", "clickhouse")
- defer db.ClickHouseReader.Close()
- defer db.ClickHouseWriter.Close()
+ // db.ClickHouseWriter, db.ClickHouseReader = db.MustInitDB(&types.DatabaseConfig{
+ // Username: cfg.ClickHouse.WriterDatabase.Username,
+ // Password: cfg.ClickHouse.WriterDatabase.Password,
+ // Name: cfg.ClickHouse.WriterDatabase.Name,
+ // Host: cfg.ClickHouse.WriterDatabase.Host,
+ // Port: cfg.ClickHouse.WriterDatabase.Port,
+ // MaxOpenConns: cfg.ClickHouse.WriterDatabase.MaxOpenConns,
+ // SSL: true,
+ // MaxIdleConns: cfg.ClickHouse.WriterDatabase.MaxIdleConns,
+ // }, &types.DatabaseConfig{
+ // Username: cfg.ClickHouse.ReaderDatabase.Username,
+ // Password: cfg.ClickHouse.ReaderDatabase.Password,
+ // Name: cfg.ClickHouse.ReaderDatabase.Name,
+ // Host: cfg.ClickHouse.ReaderDatabase.Host,
+ // Port: cfg.ClickHouse.ReaderDatabase.Port,
+ // MaxOpenConns: cfg.ClickHouse.ReaderDatabase.MaxOpenConns,
+ // SSL: true,
+ // MaxIdleConns: cfg.ClickHouse.ReaderDatabase.MaxIdleConns,
+ // }, "clickhouse", "clickhouse")
+ // defer db.ClickHouseReader.Close()
+ // defer db.ClickHouseWriter.Close()
// Initialize the persistent redis client
rdc := redis.NewClient(&redis.Options{
@@ -456,6 +457,8 @@ func Run() {
err = fixEns(erigonClient)
case "fix-ens-addresses":
err = fixEnsAddresses(erigonClient)
+ case "collect-notifications":
+ err = collectNotifications(opts.StartEpoch, opts.EndEpoch)
default:
log.Fatal(nil, fmt.Sprintf("unknown command %s", opts.Command), 0)
}
@@ -467,6 +470,19 @@ func Run() {
}
}
+func collectNotifications(startEpoch, endEpoch uint64) error {
+ epoch := startEpoch
+
+ log.Infof("collecting notifications for epoch %v", epoch)
+ notifications, err := notification.GetNotificationsForEpoch(utils.Config.Notifications.PubkeyCachePath, epoch)
+ if err != nil {
+ return err
+ }
+
+ log.Infof("found %v notifications for epoch %v", len(notifications), epoch)
+ return nil
+}
+
func fixEns(erigonClient *rpc.ErigonClient) error {
log.Infof("command: fix-ens")
addrs := []struct {
diff --git a/backend/pkg/commons/types/frontend.go b/backend/pkg/commons/types/frontend.go
index 767aca38b..eb1c04472 100644
--- a/backend/pkg/commons/types/frontend.go
+++ b/backend/pkg/commons/types/frontend.go
@@ -28,10 +28,10 @@ type NotificationsPerUserId map[UserId]map[EventName]map[EventFilter]Notificatio
func (npui NotificationsPerUserId) AddNotification(n Notification) {
if n.GetUserId() == 0 {
- log.Fatal(fmt.Errorf("Notification user id is 0"), fmt.Sprintf("Notification: %v", n), 0)
+ log.Fatal(fmt.Errorf("Notification user id is 0"), fmt.Sprintf("Notification: %v", n), 1)
}
if n.GetEventName() == "" {
- log.Fatal(fmt.Errorf("Notification event name is empty"), fmt.Sprintf("Notification: %v", n), 0)
+ log.Fatal(fmt.Errorf("Notification event name is empty"), fmt.Sprintf("Notification: %v", n), 1)
}
// next check is disabled as there are events that do not require a filter (rocketpool, network events)
// if n.GetEventFilter() == "" {
diff --git a/backend/pkg/notification/db.go b/backend/pkg/notification/db.go
index 6e19acf3b..1f999a411 100644
--- a/backend/pkg/notification/db.go
+++ b/backend/pkg/notification/db.go
@@ -3,6 +3,7 @@ package notification
import (
"github.com/doug-martin/goqu/v9"
"github.com/gobitfly/beaconchain/pkg/commons/db"
+ "github.com/gobitfly/beaconchain/pkg/commons/log"
"github.com/gobitfly/beaconchain/pkg/commons/types"
"github.com/gobitfly/beaconchain/pkg/commons/utils"
"github.com/lib/pq"
@@ -39,7 +40,8 @@ func GetSubsForEventFilter(eventName types.EventName, lastSentFilter string, las
goqu.C("last_sent_epoch"),
goqu.C("created_epoch"),
goqu.C("event_threshold"),
- ).Where(goqu.C("event_name").Eq(utils.GetNetwork() + ":" + string(eventName)))
+ goqu.C("event_name"),
+ ).Where(goqu.L("(event_name = ? AND user_id <> 0)", utils.GetNetwork()+":"+string(eventName)))
if lastSentFilter != "" {
if len(lastSentFilterArgs) > 0 {
@@ -63,6 +65,8 @@ func GetSubsForEventFilter(eventName types.EventName, lastSentFilter string, las
return nil, err
}
+ log.Infof("Found %d subscriptions for event %s", len(subs), eventName)
+
for _, sub := range subs {
if _, ok := subMap[sub.EventFilter]; !ok {
subMap[sub.EventFilter] = make([]types.Subscription, 0)
diff --git a/backend/pkg/notification/notifications.go b/backend/pkg/notification/notifications.go
index 7b148d0d5..d7cb84e97 100644
--- a/backend/pkg/notification/notifications.go
+++ b/backend/pkg/notification/notifications.go
@@ -45,6 +45,14 @@ func InitNotificationSender() {
go notificationSender()
}
+func GetNotificationsForEpoch(pubkeyCachePath string, epoch uint64) (types.NotificationsPerUserId, error) {
+ err := initPubkeyCache(pubkeyCachePath)
+ if err != nil {
+ log.Fatal(err, "error initializing pubkey cache path for notifications", 0)
+ }
+ return collectNotifications(epoch)
+}
+
func InitNotificationCollector(pubkeyCachePath string) {
err := initPubkeyCache(pubkeyCachePath)
if err != nil {
@@ -1739,8 +1747,8 @@ func collectValidatorGotSlashedNotifications(notificationsByUserID types.Notific
for _, subs := range subscribedUsers {
for _, sub := range subs {
event := pubkeyToSlashingInfoMap[sub.EventFilter]
- if event == nil {
- log.Error(fmt.Errorf("error retrieving slashing info for public key %s", sub.EventFilter), "", 0)
+ if event == nil { // pubkey has not been slashed
+ //log.Error(fmt.Errorf("error retrieving slashing info for public key %s", sub.EventFilter), "", 0)
continue
}
log.Infof("creating %v notification for validator %v in epoch %v", event.Reason, sub.EventFilter, epoch)
@@ -2386,7 +2394,7 @@ func collectNetworkNotifications(notificationsByUserID types.NotificationsPerUse
dbResult, err := GetSubsForEventFilter(
types.NetworkLivenessIncreasedEventName,
- "us.last_sent_ts <= NOW() - INTERVAL '1 hour' OR us.last_sent_ts IS NULL",
+ "(last_sent_ts <= NOW() - INTERVAL '1 hour' OR last_sent_ts IS NULL)",
nil,
nil,
)
@@ -2760,7 +2768,7 @@ func collectSyncCommittee(notificationsByUserID types.NotificationsPerUserId, ep
pubKeys = append(pubKeys, val.PubKey)
}
- dbResult, err := GetSubsForEventFilter(types.SyncCommitteeSoon, "us.last_sent_ts <= NOW() - INTERVAL '26 hours' OR us.last_sent_ts IS NULL", nil, pubKeys)
+ dbResult, err := GetSubsForEventFilter(types.SyncCommitteeSoon, "(last_sent_ts <= NOW() - INTERVAL '26 hours' OR last_sent_ts IS NULL)", nil, pubKeys)
// err = db.FrontendWriterDB.Select(&dbResult, `
// SELECT us.id, us.user_id, us.event_filter, ENCODE(us.unsubscribe_hash, 'hex') as unsubscribe_hash
// FROM users_subscriptions AS us
From d8282b1130aaf0251e5bab103767e2066a3df80a Mon Sep 17 00:00:00 2001
From: peter <1674920+peterbitfly@users.noreply.github.com>
Date: Tue, 10 Sep 2024 11:13:44 +0000
Subject: [PATCH 24/94] chore(notifications): please linter
---
backend/cmd/misc/main.go | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/backend/cmd/misc/main.go b/backend/cmd/misc/main.go
index 8af9a3499..fd77d8217 100644
--- a/backend/cmd/misc/main.go
+++ b/backend/cmd/misc/main.go
@@ -458,7 +458,7 @@ func Run() {
case "fix-ens-addresses":
err = fixEnsAddresses(erigonClient)
case "collect-notifications":
- err = collectNotifications(opts.StartEpoch, opts.EndEpoch)
+ err = collectNotifications(opts.StartEpoch)
default:
log.Fatal(nil, fmt.Sprintf("unknown command %s", opts.Command), 0)
}
@@ -470,7 +470,7 @@ func Run() {
}
}
-func collectNotifications(startEpoch, endEpoch uint64) error {
+func collectNotifications(startEpoch uint64) error {
epoch := startEpoch
log.Infof("collecting notifications for epoch %v", epoch)
From 4467399f158602a575230cd39f294448c9beda37 Mon Sep 17 00:00:00 2001
From: peter <1674920+peterbitfly@users.noreply.github.com>
Date: Tue, 10 Sep 2024 11:31:13 +0000
Subject: [PATCH 25/94] chore(notification): update firebase sdk
---
backend/go.mod | 49 +++++-----
backend/go.sum | 109 ++++++++++++----------
backend/pkg/notification/firebase.go | 30 +++---
backend/pkg/notification/notifications.go | 2 +-
4 files changed, 104 insertions(+), 86 deletions(-)
diff --git a/backend/go.mod b/backend/go.mod
index b53006df9..25788bb93 100644
--- a/backend/go.mod
+++ b/backend/go.mod
@@ -6,6 +6,7 @@ require (
cloud.google.com/go/bigtable v1.21.0
cloud.google.com/go/secretmanager v1.11.5
firebase.google.com/go v3.13.0+incompatible
+ firebase.google.com/go/v4 v4.14.1
github.com/ClickHouse/clickhouse-go/v2 v2.17.1
github.com/Gurpartap/storekit-go v0.0.0-20201205024111-36b6cd5c6a21
github.com/alexedwards/scs/redisstore v0.0.0-20240316134038-7e11d57e8885
@@ -29,7 +30,7 @@ require (
github.com/gobitfly/eth.store v0.0.0-20240312111708-b43f13990280
github.com/golang-jwt/jwt v3.2.2+incompatible
github.com/golang-jwt/jwt/v4 v4.5.0
- github.com/golang/protobuf v1.5.3
+ github.com/golang/protobuf v1.5.4
github.com/gomodule/redigo v1.9.2
github.com/google/uuid v1.6.0
github.com/gorilla/csrf v1.7.2
@@ -69,26 +70,27 @@ require (
github.com/wealdtech/go-eth2-types/v2 v2.8.2
github.com/wealdtech/go-eth2-util v1.8.0
github.com/xeipuuv/gojsonschema v1.2.0
- golang.org/x/crypto v0.19.0
+ golang.org/x/crypto v0.21.0
golang.org/x/exp v0.0.0-20240213143201-ec583247a57a
golang.org/x/sync v0.6.0
golang.org/x/text v0.14.0
golang.org/x/time v0.5.0
golang.org/x/tools v0.18.0
- google.golang.org/api v0.164.0
- google.golang.org/protobuf v1.32.0
+ google.golang.org/api v0.170.0
+ google.golang.org/protobuf v1.33.0
gopkg.in/yaml.v2 v2.4.0
)
require (
- cloud.google.com/go v0.112.0 // indirect
- cloud.google.com/go/compute v1.23.3 // indirect
+ cloud.google.com/go v0.112.1 // indirect
+ cloud.google.com/go/compute v1.24.0 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect
- cloud.google.com/go/firestore v1.14.0 // indirect
- cloud.google.com/go/iam v1.1.5 // indirect
- cloud.google.com/go/longrunning v0.5.4 // indirect
- cloud.google.com/go/storage v1.36.0 // indirect
+ cloud.google.com/go/firestore v1.15.0 // indirect
+ cloud.google.com/go/iam v1.1.7 // indirect
+ cloud.google.com/go/longrunning v0.5.5 // indirect
+ cloud.google.com/go/storage v1.40.0 // indirect
github.com/ClickHouse/ch-go v0.58.2 // indirect
+ github.com/MicahParks/keyfunc v1.9.0 // indirect
github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/TylerBrock/colorjson v0.0.0-20200706003622-8a50f05110d2 // indirect
github.com/ajg/form v1.5.1 // indirect
@@ -149,7 +151,7 @@ require (
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/s2a-go v0.1.7 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
- github.com/googleapis/gax-go/v2 v2.12.0 // indirect
+ github.com/googleapis/gax-go/v2 v2.12.3 // indirect
github.com/gorilla/securecookie v1.1.2 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 // indirect
github.com/herumi/bls-eth-go-binary v1.31.0 // indirect
@@ -241,24 +243,25 @@ require (
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect
github.com/yusufpapurcu/wmi v1.2.3 // indirect
go.opencensus.io v0.24.0 // indirect
- go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0 // indirect
- go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 // indirect
- go.opentelemetry.io/otel v1.23.0 // indirect
- go.opentelemetry.io/otel/metric v1.23.0 // indirect
- go.opentelemetry.io/otel/trace v1.23.0 // indirect
+ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect
+ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
+ go.opentelemetry.io/otel v1.24.0 // indirect
+ go.opentelemetry.io/otel/metric v1.24.0 // indirect
+ go.opentelemetry.io/otel/trace v1.24.0 // indirect
go.uber.org/atomic v1.11.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
golang.org/x/mod v0.15.0 // indirect
- golang.org/x/net v0.21.0 // indirect
- golang.org/x/oauth2 v0.17.0 // indirect
- golang.org/x/sys v0.17.0 // indirect
+ golang.org/x/net v0.23.0 // indirect
+ golang.org/x/oauth2 v0.18.0 // indirect
+ golang.org/x/sys v0.18.0 // indirect
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
google.golang.org/appengine v1.6.8 // indirect
- google.golang.org/genproto v0.0.0-20240125205218-1f4bbc51befe // indirect
- google.golang.org/genproto/googleapis/api v0.0.0-20240125205218-1f4bbc51befe // indirect
- google.golang.org/genproto/googleapis/rpc v0.0.0-20240205150955-31a09d347014 // indirect
- google.golang.org/grpc v1.62.0 // indirect
+ google.golang.org/appengine/v2 v2.0.2 // indirect
+ google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 // indirect
+ google.golang.org/genproto/googleapis/api v0.0.0-20240314234333-6e1732d8331c // indirect
+ google.golang.org/genproto/googleapis/rpc v0.0.0-20240311132316-a219d84964c2 // indirect
+ google.golang.org/grpc v1.62.1 // indirect
gopkg.in/cenkalti/backoff.v1 v1.1.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
lukechampine.com/blake3 v1.2.1 // indirect
diff --git a/backend/go.sum b/backend/go.sum
index 7d41b5693..a15806cb0 100644
--- a/backend/go.sum
+++ b/backend/go.sum
@@ -1,26 +1,28 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
-cloud.google.com/go v0.112.0 h1:tpFCD7hpHFlQ8yPwT3x+QeXqc2T6+n6T+hmABHfDUSM=
-cloud.google.com/go v0.112.0/go.mod h1:3jEEVwZ/MHU4djK5t5RHuKOA/GbLddgTdVubX1qnPD4=
+cloud.google.com/go v0.112.1 h1:uJSeirPke5UNZHIb4SxfZklVSiWWVqW4oXlETwZziwM=
+cloud.google.com/go v0.112.1/go.mod h1:+Vbu+Y1UU+I1rjmzeMOb/8RfkKJK2Gyxi1X6jJCZLo4=
cloud.google.com/go/bigtable v1.21.0 h1:BFN4jhkA9ULYYV2Ug7AeOtetVLnN2jKuIq5TcRc5C38=
cloud.google.com/go/bigtable v1.21.0/go.mod h1:V0sYNRtk0dgAKjyRr/MyBpHpSXqh+9P39euf820EZ74=
-cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk=
-cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI=
+cloud.google.com/go/compute v1.24.0 h1:phWcR2eWzRJaL/kOiJwfFsPs4BaKq1j6vnpZrc1YlVg=
+cloud.google.com/go/compute v1.24.0/go.mod h1:kw1/T+h/+tK2LJK0wiPPx1intgdAM3j/g3hFDlscY40=
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
-cloud.google.com/go/firestore v1.14.0 h1:8aLcKnMPoldYU3YHgu4t2exrKhLQkqaXAGqT0ljrFVw=
-cloud.google.com/go/firestore v1.14.0/go.mod h1:96MVaHLsEhbvkBEdZgfN+AS/GIkco1LRpH9Xp9YZfzQ=
-cloud.google.com/go/iam v1.1.5 h1:1jTsCu4bcsNsE4iiqNT5SHwrDRCfRmIaaaVFhRveTJI=
-cloud.google.com/go/iam v1.1.5/go.mod h1:rB6P/Ic3mykPbFio+vo7403drjlgvoWfYpJhMXEbzv8=
-cloud.google.com/go/longrunning v0.5.4 h1:w8xEcbZodnA2BbW6sVirkkoC+1gP8wS57EUUgGS0GVg=
-cloud.google.com/go/longrunning v0.5.4/go.mod h1:zqNVncI0BOP8ST6XQD1+VcvuShMmq7+xFSzOL++V0dI=
+cloud.google.com/go/firestore v1.15.0 h1:/k8ppuWOtNuDHt2tsRV42yI21uaGnKDEQnRFeBpbFF8=
+cloud.google.com/go/firestore v1.15.0/go.mod h1:GWOxFXcv8GZUtYpWHw/w6IuYNux/BtmeVTMmjrm4yhk=
+cloud.google.com/go/iam v1.1.7 h1:z4VHOhwKLF/+UYXAJDFwGtNF0b6gjsW1Pk9Ml0U/IoM=
+cloud.google.com/go/iam v1.1.7/go.mod h1:J4PMPg8TtyurAUvSmPj8FF3EDgY1SPRZxcUGrn7WXGA=
+cloud.google.com/go/longrunning v0.5.5 h1:GOE6pZFdSrTb4KAiKnXsJBtlE6mEyaW44oKyMILWnOg=
+cloud.google.com/go/longrunning v0.5.5/go.mod h1:WV2LAxD8/rg5Z1cNW6FJ/ZpX4E4VnDnoTk0yawPBB7s=
cloud.google.com/go/secretmanager v1.11.5 h1:82fpF5vBBvu9XW4qj0FU2C6qVMtj1RM/XHwKXUEAfYY=
cloud.google.com/go/secretmanager v1.11.5/go.mod h1:eAGv+DaCHkeVyQi0BeXgAHOU0RdrMeZIASKc+S7VqH4=
-cloud.google.com/go/storage v1.36.0 h1:P0mOkAcaJxhCTvAkMhxMfrTKiNcub4YmmPBtlhAyTr8=
-cloud.google.com/go/storage v1.36.0/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYEsng2xgOs8=
+cloud.google.com/go/storage v1.40.0 h1:VEpDQV5CJxFmJ6ueWNsKxcr1QAYOXEgxDa+sBbJahPw=
+cloud.google.com/go/storage v1.40.0/go.mod h1:Rrj7/hKlG87BLqDJYtwR0fbPld8uJPbQ2ucUMY7Ir0g=
contrib.go.opencensus.io/exporter/jaeger v0.2.1 h1:yGBYzYMewVL0yO9qqJv3Z5+IRhPdU7e9o/2oKpX4YvI=
contrib.go.opencensus.io/exporter/jaeger v0.2.1/go.mod h1:Y8IsLgdxqh1QxYxPC5IgXVmBaeLUeQFfBeBi9PbeZd0=
firebase.google.com/go v3.13.0+incompatible h1:3TdYC3DDi6aHn20qoRkxwGqNgdjtblwVAyRLQwGn/+4=
firebase.google.com/go v3.13.0+incompatible/go.mod h1:xlah6XbEyW6tbfSklcfe5FHJIwjt8toICdV5Wh9ptHs=
+firebase.google.com/go/v4 v4.14.1 h1:4qiUETaFRWoFGE1XP5VbcEdtPX93Qs+8B/7KvP2825g=
+firebase.google.com/go/v4 v4.14.1/go.mod h1:fgk2XshgNDEKaioKco+AouiegSI9oTWVqRaBdTTGBoM=
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
@@ -37,6 +39,8 @@ github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t
github.com/Gurpartap/storekit-go v0.0.0-20201205024111-36b6cd5c6a21 h1:HcdvlzaQ4CJfH7xbfJZ3ZHN//BTEpId46iKEMuP3wHE=
github.com/Gurpartap/storekit-go v0.0.0-20201205024111-36b6cd5c6a21/go.mod h1:7PODFS++oNZ6khojmPBvkrDeFO/hrc3jmvWvQAOXorw=
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
+github.com/MicahParks/keyfunc v1.9.0 h1:lhKd5xrFHLNOWrDc4Tyb/Q1AJ4LCzQ48GVJyVIID3+o=
+github.com/MicahParks/keyfunc v1.9.0/go.mod h1:IdnCilugA0O/99dW+/MkvlyrsX8+L8+x95xuVNtM5jw=
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw=
@@ -323,6 +327,7 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
+github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
@@ -334,6 +339,7 @@ github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
@@ -345,8 +351,8 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
-github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
-github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
+github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
@@ -390,8 +396,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
-github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas=
-github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU=
+github.com/googleapis/gax-go/v2 v2.12.3 h1:5/zPPDvw8Q1SuXjrqrZslrqT7dL/uJT2CQii/cLCKqA=
+github.com/googleapis/gax-go/v2 v2.12.3/go.mod h1:AKloxT6GtNbaLm8QTNSidHUVsHYcBHwWRvkNFJUQcS4=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c h1:7lF+Vz0LqiRidnzC1Oq86fpX1q/iEv2KJdrCtttYjT4=
github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
@@ -979,18 +985,18 @@ github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxt
go.mongodb.org/mongo-driver v1.11.4/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5quethTUn9WM+2g=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
-go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0 h1:UNQQKPfTDe1J81ViolILjTKPr9WetKW6uei2hFgJmFs=
-go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0/go.mod h1:r9vWsPS/3AQItv3OSlEJ/E4mbrhUbbw18meOjArPtKQ=
-go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 h1:sv9kVfal0MK0wBMCOGr+HeJm9v803BkJxGrk2au7j08=
-go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0/go.mod h1:SK2UL73Zy1quvRPonmOmRDiWk1KBV3LyIeeIxcEApWw=
-go.opentelemetry.io/otel v1.23.0 h1:Df0pqjqExIywbMCMTxkAwzjLZtRf+bBKLbUcpxO2C9E=
-go.opentelemetry.io/otel v1.23.0/go.mod h1:YCycw9ZeKhcJFrb34iVSkyT0iczq/zYDtZYFufObyB0=
-go.opentelemetry.io/otel/metric v1.23.0 h1:pazkx7ss4LFVVYSxYew7L5I6qvLXHA0Ap2pwV+9Cnpo=
-go.opentelemetry.io/otel/metric v1.23.0/go.mod h1:MqUW2X2a6Q8RN96E2/nqNoT+z9BSms20Jb7Bbp+HiTo=
-go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8=
-go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E=
-go.opentelemetry.io/otel/trace v1.23.0 h1:37Ik5Ib7xfYVb4V1UtnT97T1jI+AoIYkJyPkuL4iJgI=
-go.opentelemetry.io/otel/trace v1.23.0/go.mod h1:GSGTbIClEsuZrGIzoEHqsVfxgn5UkggkflQwDScNUsk=
+go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg=
+go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
+go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
+go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
+go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI=
+go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
+go.opentelemetry.io/otel/sdk v1.22.0 h1:6coWHw9xw7EfClIC/+O31R8IY3/+EiRFHevmHafB2Gw=
+go.opentelemetry.io/otel/sdk v1.22.0/go.mod h1:iu7luyVGYovrRpe2fmj3CVKouQNdTOkxtLzPvPz1DOc=
+go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
+go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
@@ -1032,8 +1038,8 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
-golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
+golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
+golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20240213143201-ec583247a57a h1:HinSgX1tJRX3KsL//Gxynpw5CTOAIPhgL4W8PNiIpVE=
golang.org/x/exp v0.0.0-20240213143201-ec583247a57a/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
@@ -1070,12 +1076,13 @@ golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.0.0-20220708220712-1185a9018129/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
-golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
-golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
+golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
+golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
-golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ=
-golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA=
+golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI=
+golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -1127,13 +1134,13 @@ golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
-golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
+golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
-golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U=
-golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
+golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
+golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@@ -1175,28 +1182,30 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU=
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
-google.golang.org/api v0.164.0 h1:of5G3oE2WRMVb2yoWKME4ZP8y8zpUKC6bMhxDr8ifyk=
-google.golang.org/api v0.164.0/go.mod h1:2OatzO7ZDQsoS7IFf3rvsE17/TldiU3F/zxFHeqUB5o=
+google.golang.org/api v0.170.0 h1:zMaruDePM88zxZBG+NG8+reALO2rfLhe/JShitLyT48=
+google.golang.org/api v0.170.0/go.mod h1:/xql9M2btF85xac/VAm4PsLMTLVGUOpq4BE9R8jyNy8=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
+google.golang.org/appengine/v2 v2.0.2 h1:MSqyWy2shDLwG7chbwBJ5uMyw6SNqJzhJHNDwYB0Akk=
+google.golang.org/appengine/v2 v2.0.2/go.mod h1:PkgRUWz4o1XOvbqtWTkBtCitEJ5Tp4HoVEdMMYQR/8E=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
-google.golang.org/genproto v0.0.0-20240125205218-1f4bbc51befe h1:USL2DhxfgRchafRvt/wYyyQNzwgL7ZiURcozOE/Pkvo=
-google.golang.org/genproto v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:cc8bqMqtv9gMOr0zHg2Vzff5ULhhL2IXP4sbcn32Dro=
-google.golang.org/genproto/googleapis/api v0.0.0-20240125205218-1f4bbc51befe h1:0poefMBYvYbs7g5UkjS6HcxBPaTRAmznle9jnxYoAI8=
-google.golang.org/genproto/googleapis/api v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:4jWUdICTdgc3Ibxmr8nAJiiLHwQBY0UI0XZcEMaFKaA=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20240205150955-31a09d347014 h1:FSL3lRCkhaPFxqi0s9o+V4UI2WTzAVOvkgbd4kVV4Wg=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20240205150955-31a09d347014/go.mod h1:SaPjaZGWb0lPqs6Ittu0spdfrOArqji4ZdeP5IC/9N4=
+google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 h1:9+tzLLstTlPTRyJTh+ah5wIMsBW5c4tQwGTN3thOW9Y=
+google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:mqHbVIp48Muh7Ywss/AD6I5kNVKZMmAa/QEW58Gxp2s=
+google.golang.org/genproto/googleapis/api v0.0.0-20240314234333-6e1732d8331c h1:kaI7oewGK5YnVwj+Y+EJBO/YN1ht8iTL9XkFHtVZLsc=
+google.golang.org/genproto/googleapis/api v0.0.0-20240314234333-6e1732d8331c/go.mod h1:VQW3tUculP/D4B+xVCo+VgSq8As6wA9ZjHl//pmk+6s=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240311132316-a219d84964c2 h1:9IZDv+/GcI6u+a4jRFRLxQs0RUCfavGfoOgEW6jpkI0=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240311132316-a219d84964c2/go.mod h1:UCOku4NytXMJuLQE5VuqA5lX3PcHCBo8pxNyvkf4xBs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
-google.golang.org/grpc v1.62.0 h1:HQKZ/fa1bXkX1oFOvSjmZEUL8wLSaZTjCcLAlmZRtdk=
-google.golang.org/grpc v1.62.0/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE=
+google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk=
+google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -1209,8 +1218,8 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
-google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
-google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
+google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
+google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/cenkalti/backoff.v1 v1.1.0 h1:Arh75ttbsvlpVA7WtVpH4u9h6Zl46xuptxqLxPiSo4Y=
gopkg.in/cenkalti/backoff.v1 v1.1.0/go.mod h1:J6Vskwqd+OMVJl8C33mmtxTBs2gyzfv7UDAkHu8BrjI=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/backend/pkg/notification/firebase.go b/backend/pkg/notification/firebase.go
index 91291da54..9e42cc49a 100644
--- a/backend/pkg/notification/firebase.go
+++ b/backend/pkg/notification/firebase.go
@@ -2,12 +2,13 @@ package notification
import (
"context"
+ "fmt"
"strings"
"time"
- firebase "firebase.google.com/go"
"firebase.google.com/go/messaging"
+ firebase "firebase.google.com/go/v4"
"github.com/gobitfly/beaconchain/pkg/commons/log"
"github.com/gobitfly/beaconchain/pkg/commons/utils"
"google.golang.org/api/option"
@@ -24,10 +25,10 @@ func isRelevantError(response *messaging.SendResponse) bool {
return false
}
-func SendPushBatch(messages []*messaging.Message) error {
+func SendPushBatch(messages []*messaging.Message, dryRun bool) error {
credentialsPath := utils.Config.Notifications.FirebaseCredentialsPath
if credentialsPath == "" {
- log.Error(nil, "firebase credentials path not provided, disabling push notifications", 0)
+ log.Error(fmt.Errorf("firebase credentials path not provided, disabling push notifications"), "error initializing SendPushBatch", 0)
return nil
}
@@ -42,29 +43,32 @@ func SendPushBatch(messages []*messaging.Message) error {
app, err := firebase.NewApp(context.Background(), nil, opt)
if err != nil {
- log.Error(nil, "error initializing app", 0)
+ log.Error(err, "error initializing app", 0)
return err
}
client, err := app.Messaging(ctx)
if err != nil {
- log.Error(nil, "error initializing messaging", 0)
+ log.Error(err, "error initializing messaging", 0)
return err
}
- var waitBeforeTryInSeconds = []time.Duration{0 * time.Second, 2 * time.Second, 4 * time.Second, 8 * time.Second, 16 * time.Second}
+ var waitBeforeTryInSeconds = []time.Duration{0, 2, 4, 8, 16}
var resultSuccessCount, resultFailureCount int = 0, 0
var result *messaging.BatchResponse
currentMessages := messages
tries := 0
for _, s := range waitBeforeTryInSeconds {
- time.Sleep(s)
+ time.Sleep(s * time.Second)
tries++
-
- result, err = client.SendAll(context.Background(), currentMessages)
+ if dryRun {
+ result, err = client.SendEachDryRun(context.Background(), currentMessages)
+ } else {
+ result, err = client.SendEach(context.Background(), currentMessages)
+ }
if err != nil {
- log.Error(nil, "error sending push notifications", 0)
+ log.Error(err, "error sending push notifications", 0)
return err
}
@@ -74,7 +78,9 @@ func SendPushBatch(messages []*messaging.Message) error {
newMessages := make([]*messaging.Message, 0, result.FailureCount)
if result.FailureCount > 0 {
for i, response := range result.Responses {
+ logger.Info(response)
if isRelevantError(response) {
+ logger.Infof("retrying message %d", i)
newMessages = append(newMessages, currentMessages[i])
resultFailureCount--
}
@@ -90,12 +96,12 @@ func SendPushBatch(messages []*messaging.Message) error {
if len(currentMessages) > 0 {
for _, response := range result.Responses {
if isRelevantError(response) {
- log.Error(nil, "firebase error", 0, log.Fields{"MessageID": response.MessageID, "response": response.Error})
+ logger.WithError(response.Error).WithField("MessageID", response.MessageID).Errorf("firebase error")
resultFailureCount++
}
}
}
- log.Infof("sent %d firebase notifications in %d of %d tries. successful: %d | failed: %d", len(messages), tries, len(waitBeforeTryInSeconds), resultSuccessCount, resultFailureCount)
+ logger.Infof("sent %d firebase notifications in %d of %d tries. successful: %d | failed: %d", len(messages), tries, len(waitBeforeTryInSeconds), resultSuccessCount, resultFailureCount)
return nil
}
diff --git a/backend/pkg/notification/notifications.go b/backend/pkg/notification/notifications.go
index d7cb84e97..944e2aa99 100644
--- a/backend/pkg/notification/notifications.go
+++ b/backend/pkg/notification/notifications.go
@@ -678,7 +678,7 @@ func sendPushNotifications() error {
end = len(n.Content.Messages)
}
- err = SendPushBatch(n.Content.Messages[start:end])
+ err = SendPushBatch(n.Content.Messages[start:end], false)
if err != nil {
metrics.Errors.WithLabelValues("notifications_send_push_batch").Inc()
log.Error(err, "error sending firebase batch job", 0)
From 13780b3bd18e55c854f469d4bd0f65b906531fec Mon Sep 17 00:00:00 2001
From: peter <1674920+peterbitfly@users.noreply.github.com>
Date: Tue, 10 Sep 2024 11:37:15 +0000
Subject: [PATCH 26/94] chore(notifications): fix build errors
---
backend/pkg/commons/types/frontend.go | 2 +-
backend/pkg/notification/firebase.go | 10 +++++-----
backend/pkg/notification/notifications.go | 2 +-
3 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/backend/pkg/commons/types/frontend.go b/backend/pkg/commons/types/frontend.go
index eb1c04472..a7b637d8f 100644
--- a/backend/pkg/commons/types/frontend.go
+++ b/backend/pkg/commons/types/frontend.go
@@ -10,7 +10,7 @@ import (
"strings"
"time"
- "firebase.google.com/go/messaging"
+ "firebase.google.com/go/v4/messaging"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/gobitfly/beaconchain/pkg/commons/log"
diff --git a/backend/pkg/notification/firebase.go b/backend/pkg/notification/firebase.go
index 9e42cc49a..acdf303f4 100644
--- a/backend/pkg/notification/firebase.go
+++ b/backend/pkg/notification/firebase.go
@@ -7,8 +7,8 @@ import (
"strings"
"time"
- "firebase.google.com/go/messaging"
firebase "firebase.google.com/go/v4"
+ "firebase.google.com/go/v4/messaging"
"github.com/gobitfly/beaconchain/pkg/commons/log"
"github.com/gobitfly/beaconchain/pkg/commons/utils"
"google.golang.org/api/option"
@@ -78,9 +78,9 @@ func SendPushBatch(messages []*messaging.Message, dryRun bool) error {
newMessages := make([]*messaging.Message, 0, result.FailureCount)
if result.FailureCount > 0 {
for i, response := range result.Responses {
- logger.Info(response)
+ log.Info(response)
if isRelevantError(response) {
- logger.Infof("retrying message %d", i)
+ log.Infof("retrying message %d", i)
newMessages = append(newMessages, currentMessages[i])
resultFailureCount--
}
@@ -96,12 +96,12 @@ func SendPushBatch(messages []*messaging.Message, dryRun bool) error {
if len(currentMessages) > 0 {
for _, response := range result.Responses {
if isRelevantError(response) {
- logger.WithError(response.Error).WithField("MessageID", response.MessageID).Errorf("firebase error")
+ log.Error(fmt.Errorf("firebase error, message id: %d, error: %s", response.MessageID, response.Error), "error sending push notifications", 0)
resultFailureCount++
}
}
}
- logger.Infof("sent %d firebase notifications in %d of %d tries. successful: %d | failed: %d", len(messages), tries, len(waitBeforeTryInSeconds), resultSuccessCount, resultFailureCount)
+ log.Infof("sent %d firebase notifications in %d of %d tries. successful: %d | failed: %d", len(messages), tries, len(waitBeforeTryInSeconds), resultSuccessCount, resultFailureCount)
return nil
}
diff --git a/backend/pkg/notification/notifications.go b/backend/pkg/notification/notifications.go
index 944e2aa99..352b71ca9 100644
--- a/backend/pkg/notification/notifications.go
+++ b/backend/pkg/notification/notifications.go
@@ -22,7 +22,7 @@ import (
"time"
gcp_bigtable "cloud.google.com/go/bigtable"
- "firebase.google.com/go/messaging"
+ "firebase.google.com/go/v4/messaging"
"github.com/ethereum/go-ethereum/common"
"github.com/gobitfly/beaconchain/pkg/commons/cache"
"github.com/gobitfly/beaconchain/pkg/commons/db"
From 98f7a6303facfeb26e6cb97f22625f787ba326ba Mon Sep 17 00:00:00 2001
From: peter <1674920+peterbitfly@users.noreply.github.com>
Date: Tue, 10 Sep 2024 11:39:46 +0000
Subject: [PATCH 27/94] chore(notifications): fix relevant error messages
---
backend/pkg/notification/firebase.go | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/backend/pkg/notification/firebase.go b/backend/pkg/notification/firebase.go
index acdf303f4..cd1788da5 100644
--- a/backend/pkg/notification/firebase.go
+++ b/backend/pkg/notification/firebase.go
@@ -18,7 +18,9 @@ func isRelevantError(response *messaging.SendResponse) bool {
if !response.Success && response.Error != nil {
// Ignore https://stackoverflow.com/questions/58308835/using-firebase-for-notifications-getting-app-instance-has-been-unregistered
// Errors since they indicate that the user token is expired
- if !strings.Contains(response.Error.Error(), "registration-token-not-registered") {
+ if !strings.Contains(response.Error.Error(), "registration-token-not-registered") &&
+ !strings.Contains(response.Error.Error(), "Requested entity was not found.") &&
+ !strings.Contains(response.Error.Error(), "Request contains an invalid argument.") {
return true
}
}
From 4f4367f9eff5fd24053436a29b38c697198e6f38 Mon Sep 17 00:00:00 2001
From: peter <1674920+peterbitfly@users.noreply.github.com>
Date: Tue, 10 Sep 2024 11:41:30 +0000
Subject: [PATCH 28/94] chore(notifications): disable spammy log output
---
backend/pkg/notification/firebase.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/backend/pkg/notification/firebase.go b/backend/pkg/notification/firebase.go
index cd1788da5..44551b12c 100644
--- a/backend/pkg/notification/firebase.go
+++ b/backend/pkg/notification/firebase.go
@@ -80,7 +80,7 @@ func SendPushBatch(messages []*messaging.Message, dryRun bool) error {
newMessages := make([]*messaging.Message, 0, result.FailureCount)
if result.FailureCount > 0 {
for i, response := range result.Responses {
- log.Info(response)
+ //log.Info(response)
if isRelevantError(response) {
log.Infof("retrying message %d", i)
newMessages = append(newMessages, currentMessages[i])
From d3c3a1a44b61b6a87add9d383fa3ef5a9b8cd668 Mon Sep 17 00:00:00 2001
From: peter <1674920+peterbitfly@users.noreply.github.com>
Date: Tue, 10 Sep 2024 11:44:11 +0000
Subject: [PATCH 29/94] chore(notifications): please linter
---
backend/pkg/notification/firebase.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/backend/pkg/notification/firebase.go b/backend/pkg/notification/firebase.go
index 44551b12c..3b5264c38 100644
--- a/backend/pkg/notification/firebase.go
+++ b/backend/pkg/notification/firebase.go
@@ -98,7 +98,7 @@ func SendPushBatch(messages []*messaging.Message, dryRun bool) error {
if len(currentMessages) > 0 {
for _, response := range result.Responses {
if isRelevantError(response) {
- log.Error(fmt.Errorf("firebase error, message id: %d, error: %s", response.MessageID, response.Error), "error sending push notifications", 0)
+ log.Error(fmt.Errorf("firebase error, message id: %s, error: %s", response.MessageID, response.Error), "error sending push notifications", 0)
resultFailureCount++
}
}
From 8ed5ca29394cf519809fedec4f1c5d9e9bb7746a Mon Sep 17 00:00:00 2001
From: peter <1674920+peterbitfly@users.noreply.github.com>
Date: Tue, 10 Sep 2024 11:47:31 +0000
Subject: [PATCH 30/94] chore(notifications): please linter
---
backend/pkg/notification/firebase.go | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/backend/pkg/notification/firebase.go b/backend/pkg/notification/firebase.go
index 3b5264c38..b4a75a3d6 100644
--- a/backend/pkg/notification/firebase.go
+++ b/backend/pkg/notification/firebase.go
@@ -55,14 +55,14 @@ func SendPushBatch(messages []*messaging.Message, dryRun bool) error {
return err
}
- var waitBeforeTryInSeconds = []time.Duration{0, 2, 4, 8, 16}
+ var waitBeforeTryInSeconds = []int{0, 2, 4, 8, 16}
var resultSuccessCount, resultFailureCount int = 0, 0
var result *messaging.BatchResponse
currentMessages := messages
tries := 0
for _, s := range waitBeforeTryInSeconds {
- time.Sleep(s * time.Second)
+ time.Sleep(time.Duration(s) * time.Second)
tries++
if dryRun {
result, err = client.SendEachDryRun(context.Background(), currentMessages)
From 688c3997c1ed9f1ad2f7d2d67052ceedfe95b919 Mon Sep 17 00:00:00 2001
From: peter <1674920+peterbitfly@users.noreply.github.com>
Date: Mon, 16 Sep 2024 08:48:27 +0000
Subject: [PATCH 31/94] fix(notifications): correct wrong sql syntax
---
backend/pkg/notification/db.go | 2 ++
backend/pkg/notification/notifications.go | 10 +++++-----
2 files changed, 7 insertions(+), 5 deletions(-)
diff --git a/backend/pkg/notification/db.go b/backend/pkg/notification/db.go
index 1f999a411..d065f4e87 100644
--- a/backend/pkg/notification/db.go
+++ b/backend/pkg/notification/db.go
@@ -59,6 +59,8 @@ func GetSubsForEventFilter(eventName types.EventName, lastSentFilter string, las
return nil, err
}
+ log.Info(query)
+
subMap := make(map[string][]types.Subscription, 0)
err = db.FrontendWriterDB.Select(&subs, query, args...)
if err != nil {
diff --git a/backend/pkg/notification/notifications.go b/backend/pkg/notification/notifications.go
index 352b71ca9..03b27ef44 100644
--- a/backend/pkg/notification/notifications.go
+++ b/backend/pkg/notification/notifications.go
@@ -1952,7 +1952,7 @@ func collectEthClientNotifications(notificationsByUserID types.NotificationsPerU
dbResult, err := GetSubsForEventFilter(
types.EthClientUpdateEventName,
- "(us.last_sent_ts <= NOW() - INTERVAL '2 DAY' AND TO_TIMESTAMP(?) > us.last_sent_ts) OR us.last_sent_ts IS NULL",
+ "((last_sent_ts <= NOW() - INTERVAL '2 DAY' AND TO_TIMESTAMP(?) > last_sent_ts) OR last_sent_ts IS NULL)",
[]interface{}{client.Date.Unix()},
[]string{strings.ToLower(client.Name)})
if err != nil {
@@ -2074,7 +2074,7 @@ func collectMonitoringMachine(
dbResult, err := GetSubsForEventFilter(
eventName,
- "us.created_epoch <= ? AND (us.last_sent_epoch < (? - ?) OR us.last_sent_epoch IS NULL)",
+ "(created_epoch <= ? AND (last_sent_epoch < (? - ?) OR last_sent_epoch IS NULL))",
[]interface{}{epoch, epoch, epochWaitInBetween},
nil,
)
@@ -2325,7 +2325,7 @@ func collectTaxReportNotificationNotifications(notificationsByUserID types.Notif
dbResults, err := GetSubsForEventFilter(
types.TaxReportEventName,
- "us.last_sent_ts < ? OR (us.last_sent_ts IS NULL AND us.created_ts < ?)",
+ "(last_sent_ts < ? OR (last_sent_ts IS NULL AND created_ts < ?))",
[]interface{}{firstDayOfMonth, firstDayOfMonth},
nil,
)
@@ -2487,7 +2487,7 @@ func collectRocketpoolComissionNotifications(notificationsByUserID types.Notific
dbResult, err := GetSubsForEventFilter(
types.RocketpoolCommissionThresholdEventName,
- "(us.last_sent_ts <= NOW() - INTERVAL '8 hours' OR us.last_sent_ts IS NULL) AND (us.event_threshold <= ? OR (us.event_threshold < 0 AND us.event_threshold * -1 >= ?)",
+ "(last_sent_ts <= NOW() - INTERVAL '8 hours' OR last_sent_ts IS NULL) AND (event_threshold <= ? OR (event_threshold < 0 AND event_threshold * -1 >= ?))",
[]interface{}{fee, fee},
nil,
)
@@ -2539,7 +2539,7 @@ func collectRocketpoolRewardClaimRoundNotifications(notificationsByUserID types.
dbResult, err := GetSubsForEventFilter(
types.RocketpoolNewClaimRoundStartedEventName,
- "us.last_sent_ts <= NOW() - INTERVAL '5 hours' OR us.last_sent_ts IS NULL",
+ "(last_sent_ts <= NOW() - INTERVAL '5 hours' OR last_sent_ts IS NULL)",
nil,
nil,
)
From c141b4c79e7ee6ffac857ede972a48be72ef82b6 Mon Sep 17 00:00:00 2001
From: peter <1674920+peterbitfly@users.noreply.github.com>
Date: Tue, 17 Sep 2024 09:43:56 +0000
Subject: [PATCH 32/94] fix(notifications): add last sent field to webhook data
retrieval query
---
backend/pkg/notification/notifications.go | 1 +
1 file changed, 1 insertion(+)
diff --git a/backend/pkg/notification/notifications.go b/backend/pkg/notification/notifications.go
index 03b27ef44..357c81a35 100644
--- a/backend/pkg/notification/notifications.go
+++ b/backend/pkg/notification/notifications.go
@@ -837,6 +837,7 @@ func queueWebhookNotifications(notificationsByUserID types.NotificationsPerUserI
url,
retries,
event_names,
+ last_sent,
destination
FROM
users_webhooks
From 3ff4be3781d99f302204338d2727a3235154e73e Mon Sep 17 00:00:00 2001
From: Patrick Pfeiffer
Date: Tue, 17 Sep 2024 13:41:50 +0200
Subject: [PATCH 33/94] chore(notifications): better check for
webhook-ratelimit
---
backend/pkg/notification/notifications.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/backend/pkg/notification/notifications.go b/backend/pkg/notification/notifications.go
index 357c81a35..89ec6c858 100644
--- a/backend/pkg/notification/notifications.go
+++ b/backend/pkg/notification/notifications.go
@@ -1164,7 +1164,7 @@ func sendDiscordNotifications() error {
resp.Body.Close()
}
- if strings.Contains(errResp.Body, "You are being rate limited") {
+ if resp.StatusCode == http.StatusTooManyRequests {
log.Warnf("could not push to discord webhook due to rate limit. %v url: %v", errResp.Body, webhook.Url)
} else {
log.Error(nil, "error pushing discord webhook", 0, map[string]interface{}{"errResp.Body": errResp.Body, "webhook.Url": webhook.Url})
From f9aecaf7bdd9f5cd63f20c6bf096a92bac6326bd Mon Sep 17 00:00:00 2001
From: LUCCA DUKIC <109136188+LuccaBitfly@users.noreply.github.com>
Date: Thu, 19 Sep 2024 10:58:52 +0200
Subject: [PATCH 34/94] (BEDS-452) move clients in notification settings
---
backend/pkg/api/types/notifications.go | 24 +++++++++++++++---------
1 file changed, 15 insertions(+), 9 deletions(-)
diff --git a/backend/pkg/api/types/notifications.go b/backend/pkg/api/types/notifications.go
index 27bf585be..5d80efc89 100644
--- a/backend/pkg/api/types/notifications.go
+++ b/backend/pkg/api/types/notifications.go
@@ -162,6 +162,12 @@ type NotificationPairedDevice struct {
}
type InternalPutUserNotificationSettingsPairedDevicesResponse ApiDataResponse[NotificationPairedDevice]
+type NotificationSettingsClients struct {
+ Id uint64 `json:"id"`
+ Name string `json:"name"`
+ Category string `json:"category"`
+ IsSubscribed bool `json:"is_subscribed"`
+}
type NotificationSettingsGeneral struct {
DoNotDisturbTimestamp int64 `json:"do_not_disturb_timestamp"` // notifications are disabled until this timestamp
IsEmailNotificationsEnabled bool `json:"is_email_notifications_enabled"`
@@ -175,18 +181,18 @@ type NotificationSettingsGeneral struct {
IsMachineMemoryUsageSubscribed bool `json:"is_machine_memory_usage_subscribed"`
MachineMemoryUsageThreshold float64 `json:"machine_memory_usage_threshold" faker:"boundary_start=0, boundary_end=1"`
- SubscribedClients []string `json:"subscribed_clients"`
- IsRocketPoolNewRewardRoundSubscribed bool `json:"is_rocket_pool_new_reward_round_subscribed"`
- IsRocketPoolMaxCollateralSubscribed bool `json:"is_rocket_pool_max_collateral_subscribed"`
- RocketPoolMaxCollateralThreshold float64 `json:"rocket_pool_max_collateral_threshold" faker:"boundary_start=0, boundary_end=1"`
- IsRocketPoolMinCollateralSubscribed bool `json:"is_rocket_pool_min_collateral_subscribed"`
- RocketPoolMinCollateralThreshold float64 `json:"rocket_pool_min_collateral_threshold" faker:"boundary_start=0, boundary_end=1"`
+ IsRocketPoolNewRewardRoundSubscribed bool `json:"is_rocket_pool_new_reward_round_subscribed"`
+ IsRocketPoolMaxCollateralSubscribed bool `json:"is_rocket_pool_max_collateral_subscribed"`
+ RocketPoolMaxCollateralThreshold float64 `json:"rocket_pool_max_collateral_threshold" faker:"boundary_start=0, boundary_end=1"`
+ IsRocketPoolMinCollateralSubscribed bool `json:"is_rocket_pool_min_collateral_subscribed"`
+ RocketPoolMinCollateralThreshold float64 `json:"rocket_pool_min_collateral_threshold" faker:"boundary_start=0, boundary_end=1"`
}
type InternalPutUserNotificationSettingsGeneralResponse ApiDataResponse[NotificationSettingsGeneral]
type NotificationSettings struct {
- GeneralSettings NotificationSettingsGeneral `json:"general_settings"`
- Networks []NotificationNetwork `json:"networks"`
- PairedDevices []NotificationPairedDevice `json:"paired_devices"`
+ GeneralSettings NotificationSettingsGeneral `json:"general_settings"`
+ Networks []NotificationNetwork `json:"networks"`
+ PairedDevices []NotificationPairedDevice `json:"paired_devices"`
+ Clients []NotificationSettingsClients `json:"clients"`
}
type InternalGetUserNotificationSettingsResponse ApiDataResponse[NotificationSettings]
From d0a011c6dc740ce3673464078e94cfc3afedb0dc Mon Sep 17 00:00:00 2001
From: LUCCA DUKIC <109136188+LuccaBitfly@users.noreply.github.com>
Date: Thu, 19 Sep 2024 12:29:45 +0200
Subject: [PATCH 35/94] (BEDS-452) add new client notification settings
endpoint
---
backend/pkg/api/data_access/dummy.go | 5 +
backend/pkg/api/data_access/notifications.go | 4 +
backend/pkg/api/handlers/internal.go | 4 +
backend/pkg/api/handlers/public.go | 122 +++++++++++++------
backend/pkg/api/router.go | 1 +
backend/pkg/api/types/notifications.go | 13 +-
6 files changed, 104 insertions(+), 45 deletions(-)
diff --git a/backend/pkg/api/data_access/dummy.go b/backend/pkg/api/data_access/dummy.go
index 7bee9a38c..38fb693ab 100644
--- a/backend/pkg/api/data_access/dummy.go
+++ b/backend/pkg/api/data_access/dummy.go
@@ -488,6 +488,11 @@ func (d *DummyService) UpdateNotificationSettingsPairedDevice(ctx context.Contex
func (d *DummyService) DeleteNotificationSettingsPairedDevice(ctx context.Context, userId uint64, pairedDeviceId string) error {
return nil
}
+
+func (d *DummyService) UpdateNotificationSettingsClients(ctx context.Context, userId uint64, clientId uint64, IsSubscribed bool) (*t.NotificationSettingsClient, error) {
+ return getDummyStruct[t.NotificationSettingsClient]()
+}
+
func (d *DummyService) GetNotificationSettingsDashboards(ctx context.Context, userId uint64, cursor string, colSort t.Sort[enums.NotificationSettingsDashboardColumn], search string, limit uint64) ([]t.NotificationSettingsDashboardsTableRow, *t.Paging, error) {
r, p, err := getDummyWithPaging[t.NotificationSettingsDashboardsTableRow]()
for i, n := range r {
diff --git a/backend/pkg/api/data_access/notifications.go b/backend/pkg/api/data_access/notifications.go
index 5389847db..44f7ca4a5 100644
--- a/backend/pkg/api/data_access/notifications.go
+++ b/backend/pkg/api/data_access/notifications.go
@@ -25,6 +25,7 @@ type NotificationsRepository interface {
UpdateNotificationSettingsNetworks(ctx context.Context, userId uint64, chainId uint64, settings t.NotificationSettingsNetwork) error
UpdateNotificationSettingsPairedDevice(ctx context.Context, userId uint64, pairedDeviceId string, name string, IsNotificationsEnabled bool) error
DeleteNotificationSettingsPairedDevice(ctx context.Context, userId uint64, pairedDeviceId string) error
+ UpdateNotificationSettingsClients(ctx context.Context, userId uint64, clientId uint64, IsSubscribed bool) (*t.NotificationSettingsClient, error)
GetNotificationSettingsDashboards(ctx context.Context, userId uint64, cursor string, colSort t.Sort[enums.NotificationSettingsDashboardColumn], search string, limit uint64) ([]t.NotificationSettingsDashboardsTableRow, *t.Paging, error)
UpdateNotificationSettingsValidatorDashboard(ctx context.Context, dashboardId t.VDBIdPrimary, groupId uint64, settings t.NotificationSettingsValidatorDashboard) error
UpdateNotificationSettingsAccountDashboard(ctx context.Context, dashboardId t.VDBIdPrimary, groupId uint64, settings t.NotificationSettingsAccountDashboard) error
@@ -72,6 +73,9 @@ func (d *DataAccessService) UpdateNotificationSettingsPairedDevice(ctx context.C
func (d *DataAccessService) DeleteNotificationSettingsPairedDevice(ctx context.Context, userId uint64, pairedDeviceId string) error {
return d.dummy.DeleteNotificationSettingsPairedDevice(ctx, userId, pairedDeviceId)
}
+func (d *DataAccessService) UpdateNotificationSettingsClients(ctx context.Context, userId uint64, clientId uint64, IsSubscribed bool) (*t.NotificationSettingsClient, error) {
+ return d.dummy.UpdateNotificationSettingsClients(ctx, userId, clientId, IsSubscribed)
+}
func (d *DataAccessService) GetNotificationSettingsDashboards(ctx context.Context, userId uint64, cursor string, colSort t.Sort[enums.NotificationSettingsDashboardColumn], search string, limit uint64) ([]t.NotificationSettingsDashboardsTableRow, *t.Paging, error) {
return d.dummy.GetNotificationSettingsDashboards(ctx, userId, cursor, colSort, search, limit)
}
diff --git a/backend/pkg/api/handlers/internal.go b/backend/pkg/api/handlers/internal.go
index 78d998856..bf3e347fe 100644
--- a/backend/pkg/api/handlers/internal.go
+++ b/backend/pkg/api/handlers/internal.go
@@ -609,6 +609,10 @@ func (h *HandlerService) InternalDeleteUserNotificationSettingsPairedDevices(w h
h.PublicDeleteUserNotificationSettingsPairedDevices(w, r)
}
+func (h *HandlerService) InternalPutUserNotificationSettingsClient(w http.ResponseWriter, r *http.Request) {
+ h.PublicPutUserNotificationSettingsClient(w, r)
+}
+
func (h *HandlerService) InternalGetUserNotificationSettingsDashboards(w http.ResponseWriter, r *http.Request) {
h.PublicGetUserNotificationSettingsDashboards(w, r)
}
diff --git a/backend/pkg/api/handlers/public.go b/backend/pkg/api/handlers/public.go
index e0e560b8c..d160582df 100644
--- a/backend/pkg/api/handlers/public.go
+++ b/backend/pkg/api/handlers/public.go
@@ -271,7 +271,7 @@ func (h *HandlerService) PublicGetValidatorDashboard(w http.ResponseWriter, r *h
// @Security ApiKeyInHeader || ApiKeyInQuery
// @Tags Validator Dashboard Management
// @Produce json
-// @Param dashboard_id path string true "The ID of the dashboard."
+// @Param dashboard_id path integer true "The ID of the dashboard."
// @Success 204 "Dashboard deleted successfully."
// @Failure 400 {object} types.ApiErrorResponse "Bad Request"
// @Router /validator-dashboards/{dashboard_id} [delete]
@@ -297,7 +297,7 @@ func (h *HandlerService) PublicDeleteValidatorDashboard(w http.ResponseWriter, r
// @Tags Validator Dashboard Management
// @Accept json
// @Produce json
-// @Param dashboard_id path string true "The ID of the dashboard."
+// @Param dashboard_id path integer true "The ID of the dashboard."
// @Param request body handlers.PublicPutValidatorDashboardName.request true "request"
// @Success 200 {object} types.ApiDataResponse[types.VDBPostReturnData]
// @Failure 400 {object} types.ApiErrorResponse
@@ -336,7 +336,7 @@ func (h *HandlerService) PublicPutValidatorDashboardName(w http.ResponseWriter,
// @Tags Validator Dashboard Management
// @Accept json
// @Produce json
-// @Param dashboard_id path string true "The ID of the dashboard."
+// @Param dashboard_id path integer true "The ID of the dashboard."
// @Param request body handlers.PublicPostValidatorDashboardGroups.request true "request"
// @Success 201 {object} types.ApiDataResponse[types.VDBPostCreateGroupData]
// @Failure 400 {object} types.ApiErrorResponse
@@ -400,8 +400,8 @@ func (h *HandlerService) PublicPostValidatorDashboardGroups(w http.ResponseWrite
// @Tags Validator Dashboard Management
// @Accept json
// @Produce json
-// @Param dashboard_id path string true "The ID of the dashboard."
-// @Param group_id path string true "The ID of the group."
+// @Param dashboard_id path integer true "The ID of the dashboard."
+// @Param group_id path integer true "The ID of the group."
// @Param request body handlers.PublicPutValidatorDashboardGroups.request true "request"
// @Success 200 {object} types.ApiDataResponse[types.VDBPostCreateGroupData]
// @Failure 400 {object} types.ApiErrorResponse
@@ -453,8 +453,8 @@ func (h *HandlerService) PublicPutValidatorDashboardGroups(w http.ResponseWriter
// @Security ApiKeyInHeader || ApiKeyInQuery
// @Accept json
// @Produce json
-// @Param dashboard_id path string true "The ID of the dashboard."
-// @Param group_id path string true "The ID of the group."
+// @Param dashboard_id path integer true "The ID of the dashboard."
+// @Param group_id path integer true "The ID of the group."
// @Success 204 "Group deleted successfully."
// @Failure 400 {object} types.ApiErrorResponse
// @Router /validator-dashboards/{dashboard_id}/groups/{group_id} [delete]
@@ -496,7 +496,7 @@ func (h *HandlerService) PublicDeleteValidatorDashboardGroup(w http.ResponseWrit
// @Tags Validator Dashboard Management
// @Accept json
// @Produce json
-// @Param dashboard_id path string true "The ID of the dashboard."
+// @Param dashboard_id path integer true "The ID of the dashboard."
// @Param request body handlers.PublicPostValidatorDashboardValidators.request true "`group_id`: (optional) Provide a single group id, to which all validators get added to. If omitted, the default group will be used.
To add validators, only one of the following fields can be set:
`validators`: Provide a list of validator indices or public keys to add to the dashboard.
`deposit_address`: (limited to subscription tiers with 'Bulk adding') Provide a deposit address from which as many validators as possible will be added to the dashboard.
`withdrawal_address`: (limited to subscription tiers with 'Bulk adding') Provide a withdrawal address from which as many validators as possible will be added to the dashboard.
`graffiti`: (limited to subscription tiers with 'Bulk adding') Provide a graffiti string from which as many validators as possible will be added to the dashboard.
"
// @Success 201 {object} types.ApiDataResponse[[]types.VDBPostValidatorsData] "Returns a list of added validators."
// @Failure 400 {object} types.ApiErrorResponse
@@ -631,7 +631,7 @@ func (h *HandlerService) PublicPostValidatorDashboardValidators(w http.ResponseW
// @Tags Validator Dashboard
// @Produce json
// @Param dashboard_id path string true "The ID of the dashboard."
-// @Param group_id query string false "The ID of the group."
+// @Param group_id query integer false "The ID of the group."
// @Param limit query string false "The maximum number of results that may be returned."
// @Param sort query string false "The field you want to sort by. Append with `:desc` for descending order." Enums(index, public_key, balance, status, withdrawal_credentials)
// @Param search query string false "Search for Address, ENS."
@@ -672,7 +672,7 @@ func (h *HandlerService) PublicGetValidatorDashboardValidators(w http.ResponseWr
// @Tags Validator Dashboard Management
// @Accept json
// @Produce json
-// @Param dashboard_id path string true "The ID of the dashboard."
+// @Param dashboard_id path integer true "The ID of the dashboard."
// @Param request body handlers.PublicDeleteValidatorDashboardValidators.request true "`validators`: Provide an array of validator indices or public keys that should get removed from the dashboard."
// @Success 204 "Validators removed successfully."
// @Failure 400 {object} types.ApiErrorResponse
@@ -714,7 +714,7 @@ func (h *HandlerService) PublicDeleteValidatorDashboardValidators(w http.Respons
// @Tags Validator Dashboard Management
// @Accept json
// @Produce json
-// @Param dashboard_id path string true "The ID of the dashboard."
+// @Param dashboard_id path integer true "The ID of the dashboard."
// @Param request body handlers.PublicPostValidatorDashboardPublicIds.request true "`name`: Provide a public name for the dashboard `share_settings`:
`share_groups`: If set to `true`, accessing the dashboard through the public ID will not reveal any group information.
"
// @Success 201 {object} types.ApiDataResponse[types.VDBPublicId]
// @Failure 400 {object} types.ApiErrorResponse
@@ -768,7 +768,7 @@ func (h *HandlerService) PublicPostValidatorDashboardPublicIds(w http.ResponseWr
// @Tags Validator Dashboard Management
// @Accept json
// @Produce json
-// @Param dashboard_id path string true "The ID of the dashboard."
+// @Param dashboard_id path integer true "The ID of the dashboard."
// @Param public_id path string true "The ID of the public ID."
// @Param request body handlers.PublicPutValidatorDashboardPublicId.request true "`name`: Provide a public name for the dashboard `share_settings`:
`share_groups`: If set to `true`, accessing the dashboard through the public ID will not reveal any group information.
"
// @Success 200 {object} types.ApiDataResponse[types.VDBPublicId]
@@ -823,7 +823,7 @@ func (h *HandlerService) PublicPutValidatorDashboardPublicId(w http.ResponseWrit
// @Security ApiKeyInHeader || ApiKeyInQuery
// @Tags Validator Dashboard Management
// @Produce json
-// @Param dashboard_id path string true "The ID of the dashboard."
+// @Param dashboard_id path integer true "The ID of the dashboard."
// @Param public_id path string true "The ID of the public ID."
// @Success 204 "Public ID deleted successfully."
// @Failure 400 {object} types.ApiErrorResponse
@@ -863,8 +863,8 @@ func (h *HandlerService) PublicDeleteValidatorDashboardPublicId(w http.ResponseW
// @Tags Validator Dashboard Management
// @Accept json
// @Produce json
-// @Param dashboard_id path string true "The ID of the dashboard."
-// @Param request body handlers.PublicPutValidatorDashboardArchiving.request true "request"
+// @Param dashboard_id path integer true "The ID of the dashboard."
+// @Param request body handlers.PublicPutValidatorDashboardArchiving.request true "`is_archived`: Set to `true` to archive the dashboard, or `false` to unarchive it."
// @Success 200 {object} types.ApiDataResponse[types.VDBPostArchivingReturnData]
// @Failure 400 {object} types.ApiErrorResponse
// @Failure 409 {object} types.ApiErrorResponse "Conflict. The request could not be performed by the server because the authenticated user has already reached their subscription limit."
@@ -1043,7 +1043,7 @@ func (h *HandlerService) PublicGetValidatorDashboardSummary(w http.ResponseWrite
// @Tags Validator Dashboard
// @Produce json
// @Param dashboard_id path string true "The ID of the dashboard."
-// @Param group_id path string true "The ID of the group."
+// @Param group_id path integer true "The ID of the group."
// @Param period query string true "Time period to get data for." Enums(all_time, last_30d, last_7d, last_24h, last_1h)
// @Param modes query string false "Provide a comma separated list of protocol modes which should be respected for validator calculations. Possible values are `rocket_pool``."
// @Success 200 {object} types.GetValidatorDashboardGroupSummaryResponse
@@ -1142,7 +1142,7 @@ func (h *HandlerService) PublicGetValidatorDashboardSummaryChart(w http.Response
// @Tags Validator Dashboard
// @Produce json
// @Param dashboard_id path string true "The ID of the dashboard."
-// @Param group_id query string false "The ID of the group."
+// @Param group_id query integer false "The ID of the group."
// @Param duty query string false "Validator duty to get data for." Enums(none, sync, slashed, proposal) Default(none)
// @Param period query string true "Time period to get data for." Enums(all_time, last_30d, last_7d, last_24h, last_1h)
// @Success 200 {object} types.GetValidatorDashboardSummaryValidatorsResponse
@@ -1246,8 +1246,8 @@ func (h *HandlerService) PublicGetValidatorDashboardRewards(w http.ResponseWrite
// @Tags Validator Dashboard
// @Produce json
// @Param dashboard_id path string true "The ID of the dashboard."
-// @Param group_id path string true "The ID of the group."
-// @Param epoch path string true "The epoch to get data for."
+// @Param group_id path integer true "The ID of the group."
+// @Param epoch path integer true "The epoch to get data for."
// @Param modes query string false "Provide a comma separated list of protocol modes which should be respected for validator calculations. Possible values are `rocket_pool``."
// @Success 200 {object} types.GetValidatorDashboardGroupRewardsResponse
// @Failure 400 {object} types.ApiErrorResponse
@@ -1322,8 +1322,8 @@ func (h *HandlerService) PublicGetValidatorDashboardRewardsChart(w http.Response
// @Tags Validator Dashboard
// @Produce json
// @Param dashboard_id path string true "The ID of the dashboard."
-// @Param epoch path string true "The epoch to get data for."
-// @Param group_id query string false "The ID of the group."
+// @Param epoch path integer true "The epoch to get data for."
+// @Param group_id query integer false "The ID of the group."
// @Param cursor query string false "Return data for the given cursor value. Pass the `paging.next_cursor`` value of the previous response to navigate to forward, or pass the `paging.prev_cursor`` value of the previous response to navigate to backward."
// @Param limit query string false "The maximum number of results that may be returned."
// @Param sort query string false "The field you want to sort by. Append with `:desc` for descending order." Enums(validator, reward)
@@ -1460,8 +1460,8 @@ func (h *HandlerService) PublicGetValidatorDashboardHeatmap(w http.ResponseWrite
// @Tags Validator Dashboard
// @Produce json
// @Param dashboard_id path string true "The ID of the dashboard."
-// @Param group_id path string true "The ID of the group."
-// @Param timestamp path string true "The timestamp to get data for."
+// @Param group_id path integer true "The ID of the group."
+// @Param timestamp path integer true "The timestamp to get data for."
// @Param modes query string false "Provide a comma separated list of protocol modes which should be respected for validator calculations. Possible values are `rocket_pool``."
// @Param aggregation query string false "Aggregation type to get data for." Enums(epoch, hourly, daily, weekly) Default(hourly)
// @Success 200 {object} types.GetValidatorDashboardGroupHeatmapResponse
@@ -1903,7 +1903,7 @@ func (h *HandlerService) PublicGetUserNotifications(w http.ResponseWriter, r *ht
// @Produce json
// @Param network query string false "If set, results will be filtered to only include networks given. Provide a comma separated list."
// @Param cursor query string false "Return data for the given cursor value. Pass the `paging.next_cursor`` value of the previous response to navigate to forward, or pass the `paging.prev_cursor`` value of the previous response to navigate to backward."
-// @Param limit query string false "The maximum number of results that may be returned."
+// @Param limit query integer false "The maximum number of results that may be returned."
// @Param sort query string false "The field you want to sort by. Append with `:desc` for descending order." " Enums(chain_id, timestamp, dashboard_id)
// @Param search query string false "Search for Dashboard, Group"
// @Success 200 {object} types.InternalGetUserNotificationDashboardsResponse
@@ -1931,7 +1931,7 @@ func (h *HandlerService) PublicGetUserNotificationDashboards(w http.ResponseWrit
}
response := types.InternalGetUserNotificationDashboardsResponse{
Data: data,
- Paging: *paging,
+ Paging: *paging, // @Param epoch path strings
}
returnOk(w, r, response)
}
@@ -1943,8 +1943,8 @@ func (h *HandlerService) PublicGetUserNotificationDashboards(w http.ResponseWrit
// @Tags Notifications
// @Produce json
// @Param dashboard_id path string true "The ID of the dashboard."
-// @Param group_id path string true "The ID of the group."
-// @Param epoch path string true "The epoch of the notification."
+// @Param group_id path integer true "The ID of the group."
+// @Param epoch path integer true "The epoch of the notification."
// @Success 200 {object} types.InternalGetUserNotificationsValidatorDashboardResponse
// @Failure 400 {object} types.ApiErrorResponse
// @Router /users/me/notifications/validator-dashboards/{dashboard_id}/groups/{group_id}/epochs/{epoch} [get]
@@ -1976,8 +1976,8 @@ func (h *HandlerService) PublicGetUserNotificationsValidatorDashboard(w http.Res
// @Tags Notifications
// @Produce json
// @Param dashboard_id path string true "The ID of the dashboard."
-// @Param group_id path string true "The ID of the group."
-// @Param epoch path string true "The epoch of the notification."
+// @Param group_id path integer true "The ID of the group."
+// @Param epoch path integer true "The epoch of the notification."
// @Success 200 {object} types.InternalGetUserNotificationsAccountDashboardResponse
// @Failure 400 {object} types.ApiErrorResponse
// @Router /users/me/notifications/account-dashboards/{dashboard_id}/groups/{group_id}/epochs/{epoch} [get]
@@ -2009,7 +2009,7 @@ func (h *HandlerService) PublicGetUserNotificationsAccountDashboard(w http.Respo
// @Tags Notifications
// @Produce json
// @Param cursor query string false "Return data for the given cursor value. Pass the `paging.next_cursor`` value of the previous response to navigate to forward, or pass the `paging.prev_cursor`` value of the previous response to navigate to backward."
-// @Param limit query string false "The maximum number of results that may be returned."
+// @Param limit query integer false "The maximum number of results that may be returned."
// @Param sort query string false "The field you want to sort by. Append with `:desc` for descending order." Enums(machine_name, threshold, event_type, timestamp)
// @Param search query string false "Search for Machine"
// @Success 200 {object} types.InternalGetUserNotificationMachinesResponse
@@ -2048,7 +2048,7 @@ func (h *HandlerService) PublicGetUserNotificationMachines(w http.ResponseWriter
// @Tags Notifications
// @Produce json
// @Param cursor query string false "Return data for the given cursor value. Pass the `paging.next_cursor`` value of the previous response to navigate to forward, or pass the `paging.prev_cursor`` value of the previous response to navigate to backward."
-// @Param limit query string false "The maximum number of results that may be returned."
+// @Param limit query integer false "The maximum number of results that may be returned."
// @Param sort query string false "The field you want to sort by. Append with `:desc` for descending order." Enums(client_name, timestamp)
// @Param search query string false "Search for Client"
// @Success 200 {object} types.InternalGetUserNotificationClientsResponse
@@ -2087,7 +2087,7 @@ func (h *HandlerService) PublicGetUserNotificationClients(w http.ResponseWriter,
// @Tags Notifications
// @Produce json
// @Param cursor query string false "Return data for the given cursor value. Pass the `paging.next_cursor`` value of the previous response to navigate to forward, or pass the `paging.prev_cursor`` value of the previous response to navigate to backward."
-// @Param limit query string false "The maximum number of results that may be returned."
+// @Param limit query integer false "The maximum number of results that may be returned."
// @Param sort query string false "The field you want to sort by. Append with `:desc` for descending order." Enums(timestamp, event_type, node_address)
// @Param search query string false "Search for TODO"
// @Success 200 {object} types.InternalGetUserNotificationRocketPoolResponse
@@ -2126,7 +2126,7 @@ func (h *HandlerService) PublicGetUserNotificationRocketPool(w http.ResponseWrit
// @Tags Notifications
// @Produce json
// @Param cursor query string false "Return data for the given cursor value. Pass the `paging.next_cursor`` value of the previous response to navigate to forward, or pass the `paging.prev_cursor`` value of the previous response to navigate to backward."
-// @Param limit query string false "The maximum number of results that may be returned."
+// @Param limit query integer false "The maximum number of results that may be returned."
// @Param sort query string false "The field you want to sort by. Append with `:desc` for descending order." Enums(timestamp, event_type)
// @Param search query string false "Search for TODO"
// @Success 200 {object} types.InternalGetUserNotificationNetworksResponse
@@ -2190,7 +2190,7 @@ func (h *HandlerService) PublicGetUserNotificationSettings(w http.ResponseWriter
// @Tags Notification Settings
// @Accept json
// @Produce json
-// @Param request body types.NotificationSettingsGeneral true "Notification settings"
+// @Param request body types.NotificationSettingsGeneral true "Description TODO"
// @Success 200 {object} types.InternalPutUserNotificationSettingsGeneralResponse
// @Failure 400 {object} types.ApiErrorResponse
// @Router /users/me/notifications/settings/general [put]
@@ -2211,7 +2211,6 @@ func (h *HandlerService) PublicPutUserNotificationSettingsGeneral(w http.Respons
checkMinMax(&v, req.MachineMemoryUsageThreshold, 0, 1, "machine_memory_usage_threshold")
checkMinMax(&v, req.RocketPoolMaxCollateralThreshold, 0, 1, "rocket_pool_max_collateral_threshold")
checkMinMax(&v, req.RocketPoolMinCollateralThreshold, 0, 1, "rocket_pool_min_collateral_threshold")
- // TODO: check validity of clients
if v.hasErrors() {
handleErr(w, r, v)
return
@@ -2235,7 +2234,7 @@ func (h *HandlerService) PublicPutUserNotificationSettingsGeneral(w http.Respons
// @Accept json
// @Produce json
// @Param network path string true "The networks name or chain ID."
-// @Param request body types.NotificationSettingsNetwork true "Notification settings"
+// @Param request body types.NotificationSettingsNetwork true "Description Todo"
// @Success 200 {object} types.InternalPutUserNotificationSettingsNetworksResponse
// @Failure 400 {object} types.ApiErrorResponse
// @Router /users/me/notifications/settings/networks/{network} [put]
@@ -2280,7 +2279,7 @@ func (h *HandlerService) PublicPutUserNotificationSettingsNetworks(w http.Respon
// @Accept json
// @Produce json
// @Param paired_device_id path string true "The paired device ID."
-// @Param request body handlers.PublicPutUserNotificationSettingsPairedDevices.request true "Notification settings"
+// @Param request body handlers.PublicPutUserNotificationSettingsPairedDevices.request true "Description TODO"
// @Success 200 {object} types.InternalPutUserNotificationSettingsPairedDevicesResponse
// @Failure 400 {object} types.ApiErrorResponse
// @Router /users/me/notifications/settings/paired-devices/{paired_device_id} [put]
@@ -2355,6 +2354,49 @@ func (h *HandlerService) PublicDeleteUserNotificationSettingsPairedDevices(w htt
returnNoContent(w, r)
}
+// PublicPutUserNotificationSettingsClient godoc
+//
+// @Description Update client notification settings for the authenticated user. When a client is subscribed, notifications will be sent when a new version is available.
+// @Security ApiKeyInHeader || ApiKeyInQuery
+// @Tags Notification Settings
+// @Accept json
+// @Produce json
+// @Param client_id path integer true "The ID of the client."
+// @Param request body handlers.PublicPutUserNotificationSettingsClient.request true "`is_subscribed`: Set to `true` to subscribe to notifications; set to `false` to unsubscribe."
+// @Success 200 {object} types.InternalPutUserNotificationSettingsClientResponse
+// @Failure 400 {object} types.ApiErrorResponse
+// @Router /users/me/notifications/settings/clients/{client_id} [put]
+func (h *HandlerService) PublicPutUserNotificationSettingsClient(w http.ResponseWriter, r *http.Request) {
+ var v validationError
+ userId, err := GetUserIdByContext(r)
+ if err != nil {
+ handleErr(w, r, err)
+ return
+ }
+ type request struct {
+ IsSubscribed bool `json:"is_subscribed"`
+ }
+ var req request
+ if err := v.checkBody(&req, r); err != nil {
+ handleErr(w, r, err)
+ return
+ }
+ clientId := v.checkUint(mux.Vars(r)["client_id"], "client_id")
+ if v.hasErrors() {
+ handleErr(w, r, v)
+ return
+ }
+ data, err := h.dai.UpdateNotificationSettingsClients(r.Context(), userId, clientId, req.IsSubscribed)
+ if err != nil {
+ handleErr(w, r, err)
+ return
+ }
+ response := types.InternalPutUserNotificationSettingsClientResponse{
+ Data: *data,
+ }
+ returnOk(w, r, response)
+}
+
// PublicGetUserNotificationSettingsDashboards godoc
//
// @Description Get a list of notification settings for the dashboards of the authenticated user.
@@ -2362,7 +2404,7 @@ func (h *HandlerService) PublicDeleteUserNotificationSettingsPairedDevices(w htt
// @Tags Notification Settings
// @Produce json
// @Param cursor query string false "Return data for the given cursor value. Pass the `paging.next_cursor`` value of the previous response to navigate to forward, or pass the `paging.prev_cursor`` value of the previous response to navigate to backward."
-// @Param limit query string false "The maximum number of results that may be returned."
+// @Param limit query integer false "The maximum number of results that may be returned."
// @Param sort query string false "The field you want to sort by. Append with `:desc` for descending order." Enums (dashboard_id, group_name)
// @Param search query string false "Search for Dashboard, Group"
// @Success 200 {object} types.InternalGetUserNotificationSettingsDashboardsResponse
@@ -2402,7 +2444,7 @@ func (h *HandlerService) PublicGetUserNotificationSettingsDashboards(w http.Resp
// @Accept json
// @Produce json
// @Param dashboard_id path string true "The ID of the dashboard."
-// @Param group_id path string true "The ID of the group."
+// @Param group_id path integer true "The ID of the group."
// @Param request body types.NotificationSettingsValidatorDashboard true "Notification settings"
// @Success 200 {object} types.InternalPutUserNotificationSettingsValidatorDashboardResponse
// @Failure 400 {object} types.ApiErrorResponse
@@ -2441,7 +2483,7 @@ func (h *HandlerService) PublicPutUserNotificationSettingsValidatorDashboard(w h
// @Accept json
// @Produce json
// @Param dashboard_id path string true "The ID of the dashboard."
-// @Param group_id path string true "The ID of the group."
+// @Param group_id path integer true "The ID of the group."
// @Param request body handlers.PublicPutUserNotificationSettingsAccountDashboard.request true "Notification settings"
// @Success 200 {object} types.InternalPutUserNotificationSettingsAccountDashboardResponse
// @Failure 400 {object} types.ApiErrorResponse
diff --git a/backend/pkg/api/router.go b/backend/pkg/api/router.go
index 16ab4c0b2..864cb69d8 100644
--- a/backend/pkg/api/router.go
+++ b/backend/pkg/api/router.go
@@ -329,6 +329,7 @@ func addNotificationRoutes(hs *handlers.HandlerService, publicRouter, internalRo
{http.MethodPut, "/settings/networks/{network}", hs.PublicPutUserNotificationSettingsNetworks, hs.InternalPutUserNotificationSettingsNetworks},
{http.MethodPut, "/settings/paired-devices/{paired_device_id}", hs.PublicPutUserNotificationSettingsPairedDevices, hs.InternalPutUserNotificationSettingsPairedDevices},
{http.MethodDelete, "/settings/paired-devices/{paired_device_id}", hs.PublicDeleteUserNotificationSettingsPairedDevices, hs.InternalDeleteUserNotificationSettingsPairedDevices},
+ {http.MethodPut, "/settings/clients/{client_id}", hs.PublicPutUserNotificationSettingsClient, hs.InternalPutUserNotificationSettingsClient},
{http.MethodGet, "/settings/dashboards", hs.PublicGetUserNotificationSettingsDashboards, hs.InternalGetUserNotificationSettingsDashboards},
{http.MethodPost, "/test-email", hs.PublicPostUserNotificationsTestEmail, hs.InternalPostUserNotificationsTestEmail},
{http.MethodPost, "/test-push", hs.PublicPostUserNotificationsTestPush, hs.InternalPostUserNotificationsTestPush},
diff --git a/backend/pkg/api/types/notifications.go b/backend/pkg/api/types/notifications.go
index 5d80efc89..4d512ff5a 100644
--- a/backend/pkg/api/types/notifications.go
+++ b/backend/pkg/api/types/notifications.go
@@ -162,12 +162,15 @@ type NotificationPairedDevice struct {
}
type InternalPutUserNotificationSettingsPairedDevicesResponse ApiDataResponse[NotificationPairedDevice]
-type NotificationSettingsClients struct {
+type NotificationSettingsClient struct {
Id uint64 `json:"id"`
Name string `json:"name"`
Category string `json:"category"`
IsSubscribed bool `json:"is_subscribed"`
}
+
+type InternalPutUserNotificationSettingsClientResponse ApiDataResponse[NotificationSettingsClient]
+
type NotificationSettingsGeneral struct {
DoNotDisturbTimestamp int64 `json:"do_not_disturb_timestamp"` // notifications are disabled until this timestamp
IsEmailNotificationsEnabled bool `json:"is_email_notifications_enabled"`
@@ -189,10 +192,10 @@ type NotificationSettingsGeneral struct {
}
type InternalPutUserNotificationSettingsGeneralResponse ApiDataResponse[NotificationSettingsGeneral]
type NotificationSettings struct {
- GeneralSettings NotificationSettingsGeneral `json:"general_settings"`
- Networks []NotificationNetwork `json:"networks"`
- PairedDevices []NotificationPairedDevice `json:"paired_devices"`
- Clients []NotificationSettingsClients `json:"clients"`
+ GeneralSettings NotificationSettingsGeneral `json:"general_settings"`
+ Networks []NotificationNetwork `json:"networks"`
+ PairedDevices []NotificationPairedDevice `json:"paired_devices"`
+ Clients []NotificationSettingsClient `json:"clients"`
}
type InternalGetUserNotificationSettingsResponse ApiDataResponse[NotificationSettings]
From 1b7d591b99887e569067d4080006ffb59ed28022 Mon Sep 17 00:00:00 2001
From: LUCCA DUKIC <109136188+LuccaBitfly@users.noreply.github.com>
Date: Thu, 19 Sep 2024 12:30:19 +0200
Subject: [PATCH 36/94] (BEDS-452) typescript conversion
---
frontend/types/api/notifications.ts | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/frontend/types/api/notifications.ts b/frontend/types/api/notifications.ts
index 0d9f53898..57a2900d4 100644
--- a/frontend/types/api/notifications.ts
+++ b/frontend/types/api/notifications.ts
@@ -156,6 +156,13 @@ export interface NotificationPairedDevice {
is_notifications_enabled: boolean;
}
export type InternalPutUserNotificationSettingsPairedDevicesResponse = ApiDataResponse;
+export interface NotificationSettingsClient {
+ id: number /* uint64 */;
+ name: string;
+ category: string;
+ is_subscribed: boolean;
+}
+export type InternalPutUserNotificationSettingsClientResponse = ApiDataResponse;
export interface NotificationSettingsGeneral {
do_not_disturb_timestamp: number /* int64 */; // notifications are disabled until this timestamp
is_email_notifications_enabled: boolean;
@@ -167,7 +174,6 @@ export interface NotificationSettingsGeneral {
machine_cpu_usage_threshold: number /* float64 */;
is_machine_memory_usage_subscribed: boolean;
machine_memory_usage_threshold: number /* float64 */;
- subscribed_clients: string[];
is_rocket_pool_new_reward_round_subscribed: boolean;
is_rocket_pool_max_collateral_subscribed: boolean;
rocket_pool_max_collateral_threshold: number /* float64 */;
@@ -179,6 +185,7 @@ export interface NotificationSettings {
general_settings: NotificationSettingsGeneral;
networks: NotificationNetwork[];
paired_devices: NotificationPairedDevice[];
+ clients: NotificationSettingsClient[];
}
export type InternalGetUserNotificationSettingsResponse = ApiDataResponse;
export interface NotificationSettingsValidatorDashboard {
From f29cd091cd21b671a4ba63d7b94b83e10e5322ad Mon Sep 17 00:00:00 2001
From: LUCCA DUKIC <109136188+LuccaBitfly@users.noreply.github.com>
Date: Thu, 19 Sep 2024 12:48:14 +0200
Subject: [PATCH 37/94] (BEDS-452) defeat frontend typecheck
---
frontend/utils/mock.ts | 27 ++++++++++++++++++++++++++-
1 file changed, 26 insertions(+), 1 deletion(-)
diff --git a/frontend/utils/mock.ts b/frontend/utils/mock.ts
index f942d8fbb..0f5e8bc6e 100644
--- a/frontend/utils/mock.ts
+++ b/frontend/utils/mock.ts
@@ -443,6 +443,32 @@ export function simulateAPIresponseAboutNetworkList(): ApiDataResponse<
export function mockManageNotificationsGeneral(): InternalGetUserNotificationSettingsResponse {
return {
data: {
+ clients: [
+ {
+ category: 'EL',
+ id: 1,
+ is_subscribed: true,
+ name: 'EL Client 1',
+ },
+ {
+ category: 'CL',
+ id: 2,
+ is_subscribed: false,
+ name: 'CL Client 1',
+ },
+ {
+ category: 'other',
+ id: 3,
+ is_subscribed: true,
+ name: 'Other Clien 1',
+ },
+ {
+ category: 'other',
+ id: 4,
+ is_subscribed: false,
+ name: 'Other Clien 2',
+ },
+ ],
general_settings: {
do_not_disturb_timestamp: 9000,
is_email_notifications_enabled: false,
@@ -459,7 +485,6 @@ export function mockManageNotificationsGeneral(): InternalGetUserNotificationSet
machine_storage_usage_threshold: 80,
rocket_pool_max_collateral_threshold: 29823,
rocket_pool_min_collateral_threshold: 123,
- subscribed_clients: [],
},
networks: [],
paired_devices: [
From 17918e6e95e069471977143416185e184619abe0 Mon Sep 17 00:00:00 2001
From: Lucca <109136188+LuccaBitfly@users.noreply.github.com>
Date: Thu, 19 Sep 2024 13:12:31 +0200
Subject: [PATCH 38/94] (BEDS-477) add url to clients notification table (#872)
---
backend/pkg/api/types/notifications.go | 1 +
frontend/types/api/notifications.ts | 1 +
2 files changed, 2 insertions(+)
diff --git a/backend/pkg/api/types/notifications.go b/backend/pkg/api/types/notifications.go
index 27bf585be..af31fd56a 100644
--- a/backend/pkg/api/types/notifications.go
+++ b/backend/pkg/api/types/notifications.go
@@ -111,6 +111,7 @@ type InternalGetUserNotificationMachinesResponse ApiPagingResponse[NotificationM
type NotificationClientsTableRow struct {
ClientName string `json:"client_name"`
Version string `json:"version"`
+ Url string `json:"url"`
Timestamp int64 `json:"timestamp"`
}
diff --git a/frontend/types/api/notifications.ts b/frontend/types/api/notifications.ts
index 0d9f53898..1ff93b79b 100644
--- a/frontend/types/api/notifications.ts
+++ b/frontend/types/api/notifications.ts
@@ -107,6 +107,7 @@ export type InternalGetUserNotificationMachinesResponse = ApiPagingResponse;
From e0f0a76c6a5c0c6ec2f3eafe0e5c3bff79c069a0 Mon Sep 17 00:00:00 2001
From: benji-bitfly
Date: Fri, 20 Sep 2024 08:48:40 +0200
Subject: [PATCH 39/94] feat: add `notifications overview`
See: BEDS-335
---
frontend/components/BcFeatureFlag.vue | 35 +++
frontend/components/bc/BcButtonText.vue | 19 ++
.../notifications/NotificationsOverview.vue | 247 ++++++++++++++++++
frontend/locales/en.json | 30 ++-
frontend/pages/notifications.vue | 4 +-
.../useNotificationsOverviewStore.ts | 35 +++
frontend/types/customFetch.ts | 6 +
7 files changed, 374 insertions(+), 2 deletions(-)
create mode 100644 frontend/components/BcFeatureFlag.vue
create mode 100644 frontend/components/bc/BcButtonText.vue
create mode 100644 frontend/components/notifications/NotificationsOverview.vue
create mode 100644 frontend/stores/notifications/useNotificationsOverviewStore.ts
diff --git a/frontend/components/BcFeatureFlag.vue b/frontend/components/BcFeatureFlag.vue
new file mode 100644
index 000000000..53c6c6673
--- /dev/null
+++ b/frontend/components/BcFeatureFlag.vue
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
diff --git a/frontend/components/bc/BcButtonText.vue b/frontend/components/bc/BcButtonText.vue
new file mode 100644
index 000000000..60eb5acc1
--- /dev/null
+++ b/frontend/components/bc/BcButtonText.vue
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
diff --git a/frontend/components/notifications/NotificationsOverview.vue b/frontend/components/notifications/NotificationsOverview.vue
new file mode 100644
index 000000000..7eb583cd4
--- /dev/null
+++ b/frontend/components/notifications/NotificationsOverview.vue
@@ -0,0 +1,247 @@
+
+
+
+
+
+
+
diff --git a/frontend/locales/en.json b/frontend/locales/en.json
index 9a989b149..7ef60571a 100644
--- a/frontend/locales/en.json
+++ b/frontend/locales/en.json
@@ -697,7 +697,8 @@
},
"footer": {
"subscriptions": "Network ({count} Subscription) | Validators ({count} Subscriptions)"
- }
+ },
+ "title": "Network"
},
"overview": {
"email_activate": "Click here to activate Email notifications",
@@ -720,6 +721,19 @@
},
"push": "Push"
},
+ "rocketpool": {
+ "col": {
+ "node_address": "Node Address",
+ "notification": "Notification",
+ "rocketpool_subscription":"Rocketpool ({count} Subscriptions)"
+ },
+ "event_types": {
+ "collateral_max": "Max collateral reached",
+ "collateral_min": "Min collateral reached",
+ "reward_round": "New reward round"
+ },
+ "search_placeholder": "Node Address"
+ },
"subscriptions": {
"accounts": {
"all": {
diff --git a/frontend/pages/notifications.vue b/frontend/pages/notifications.vue
index d1701d51d..3c8312680 100644
--- a/frontend/pages/notifications.vue
+++ b/frontend/pages/notifications.vue
@@ -6,9 +6,7 @@ import {
faNetworkWired,
} from '@fortawesome/pro-solid-svg-icons'
import type { DynamicDialogCloseOptions } from 'primevue/dynamicdialogoptions'
-import {
- BcDialogConfirm, NotificationsNetworkTable,
-} from '#components'
+import { BcDialogConfirm } from '#components'
import type { HashTabs } from '~/types/hashTabs'
useDashboardKeyProvider('notifications')
@@ -22,42 +20,44 @@ await useAsyncData('user_dashboards', () => refreshDashboards(), { watch: [ isLo
const showInDevelopment = Boolean(useRuntimeConfig().public.showInDevelopment)
const manageNotificationsModalVisisble = ref(false)
-
+const tabKey = {
+ clients: 'clients',
+ dashboards: 'dashboards',
+ machines: 'machines',
+ network: 'network',
+ rocketpool: 'rocketpool',
+}
const tabs: HashTabs = [
{
icon: faGaugeSimpleMax,
- key: 'dashboards',
+ key: tabKey.dashboards,
title: $t('notifications.tabs.dashboards'),
},
{
disabled: !showInDevelopment,
icon: faMonitorWaveform,
- key: 'machines',
+ key: tabKey.machines,
placeholder: 'Machines coming soon!',
title: $t('notifications.tabs.machines'),
},
{
- disabled: !showInDevelopment,
icon: faBolt,
- key: 'clients',
- placeholder: 'Clients coming soon!',
+ key: tabKey.clients,
title: $t('notifications.tabs.clients'),
},
{
- disabled: !showInDevelopment,
- key: 'rocketpool',
- placeholder: 'Rocketpool coming soon!',
+ key: tabKey.rocketpool,
title: $t('notifications.tabs.rocketpool'),
},
{
- component: NotificationsNetworkTable,
- disabled: !showInDevelopment,
icon: faNetworkWired,
- key: 'network',
+ key: tabKey.network,
title: $t('notifications.tabs.network'),
},
]
+const getSlotName = (key: string) => `tab-panel-${key}`
+
useBcSeo('notifications.title')
const openManageNotifications = () => {
@@ -105,16 +105,26 @@ const openManageNotifications = () => {
class="notifications-tab-view"
panels-class="notifications-tab-panels"
>
+
+
+
+
+
+
-
-
+
-
-
+
diff --git a/frontend/stores/notifications/useNotificationsRocketpoolStore.ts b/frontend/stores/notifications/useNotificationsRocketpoolStore.ts
new file mode 100644
index 000000000..23d920926
--- /dev/null
+++ b/frontend/stores/notifications/useNotificationsRocketpoolStore.ts
@@ -0,0 +1,75 @@
+import { defineStore } from 'pinia'
+import { API_PATH } from '~/types/customFetch'
+import type { InternalGetUserNotificationRocketPoolResponse } from '~/types/api/notifications'
+import type { TableQueryParams } from '~/types/datatable'
+
+const notificationsRocketpoolStore = defineStore('notifications-rocket-pool-store', () => {
+ const data = ref()
+ return { data }
+})
+
+export function useNotificationsRocketpoolStore() {
+ const { isLoggedIn } = useUserStore()
+ const { fetch } = useCustomFetch()
+ const { data: rocketpoolNotifications } = storeToRefs(notificationsRocketpoolStore())
+
+ const {
+ cursor,
+ isStoredQuery,
+ onSort,
+ pageSize,
+ query,
+ setCursor,
+ setPageSize,
+ setSearch,
+ setStoredQuery,
+ } = useTableQuery({
+ limit: 10,
+ sort: 'timestamp:desc',
+ }, 10)
+
+ const isLoading = ref(false)
+ async function loadRocketpoolNotifications(q: TableQueryParams) {
+ isLoading.value = true
+ setStoredQuery(q)
+ try {
+ const res = await fetch(
+ API_PATH.NOTIFICATIONS_ROCKETPOOL,
+ undefined,
+ undefined,
+ q,
+ )
+ isLoading.value = false
+ if (!isStoredQuery(q)) {
+ return // in case some query params change while loading
+ }
+ rocketpoolNotifications.value = res
+ return rocketpoolNotifications.value
+ }
+ catch (e) {
+ rocketpoolNotifications.value = undefined
+ throw e
+ }
+ }
+ //
+
+ watch([ query ], ([ q ]) => {
+ if (q) {
+ isLoggedIn.value && loadRocketpoolNotifications(q)
+ }
+ },
+ { immediate: true },
+ )
+
+ return {
+ cursor,
+ isLoading,
+ onSort,
+ pageSize,
+ query,
+ rocketpoolNotifications,
+ setCursor,
+ setPageSize,
+ setSearch,
+ }
+}
diff --git a/frontend/types/customFetch.ts b/frontend/types/customFetch.ts
index b7cf641b4..f472f8e6a 100644
--- a/frontend/types/customFetch.ts
+++ b/frontend/types/customFetch.ts
@@ -46,6 +46,7 @@ export enum API_PATH {
NOTIFICATIONS_MANAGEMENT_GENERAL = '/notifications/managementGeneral',
NOTIFICATIONS_NETWORK = '/notifications/networks',
NOTIFICATIONS_OVERVIEW = '/notifications',
+ NOTIFICATIONS_ROCKETPOOL = '/notifications/rocket-pool',
NOTIFICATIONS_TEST_EMAIL = '/notifications/test_email',
NOTIFICATIONS_TEST_PUSH = '/notifications/test_push',
NOTIFICATIONS_TEST_WEBHOOK = '/users/me/notifications/test-webhook',
@@ -304,6 +305,10 @@ export const mapping: Record = {
mock: false,
path: '/users/me/notifications',
},
+ [API_PATH.NOTIFICATIONS_ROCKETPOOL]: {
+ method: 'GET',
+ path: '/users/me/notifications/rocket-pool',
+ },
[API_PATH.NOTIFICATIONS_TEST_EMAIL]: {
method: 'POST',
mock: true,
From 4600d4c8dbf793e68483f5965b4e1e1014d365b7 Mon Sep 17 00:00:00 2001
From: Lucca <109136188+LuccaBitfly@users.noreply.github.com>
Date: Wed, 25 Sep 2024 14:45:51 +0200
Subject: [PATCH 66/94] (BEDS-500) don't throw error when adding more
validators than limit allows (#877)
---
backend/pkg/api/data_access/dummy.go | 4 -
backend/pkg/api/data_access/vdb.go | 1 -
backend/pkg/api/data_access/vdb_management.go | 257 +++++++-----------
backend/pkg/api/handlers/public.go | 52 ++--
4 files changed, 129 insertions(+), 185 deletions(-)
diff --git a/backend/pkg/api/data_access/dummy.go b/backend/pkg/api/data_access/dummy.go
index 3e42c6384..643482a1e 100644
--- a/backend/pkg/api/data_access/dummy.go
+++ b/backend/pkg/api/data_access/dummy.go
@@ -253,10 +253,6 @@ func (d *DummyService) GetValidatorDashboardGroupExists(ctx context.Context, das
return true, nil
}
-func (d *DummyService) GetValidatorDashboardExistingValidatorCount(ctx context.Context, dashboardId t.VDBIdPrimary, validators []t.VDBValidator) (uint64, error) {
- return getDummyData[uint64]()
-}
-
func (d *DummyService) AddValidatorDashboardValidators(ctx context.Context, dashboardId t.VDBIdPrimary, groupId uint64, validators []t.VDBValidator) ([]t.VDBPostValidatorsData, error) {
return getDummyData[[]t.VDBPostValidatorsData]()
}
diff --git a/backend/pkg/api/data_access/vdb.go b/backend/pkg/api/data_access/vdb.go
index e7498702f..da907a294 100644
--- a/backend/pkg/api/data_access/vdb.go
+++ b/backend/pkg/api/data_access/vdb.go
@@ -28,7 +28,6 @@ type ValidatorDashboardRepository interface {
GetValidatorDashboardGroupCount(ctx context.Context, dashboardId t.VDBIdPrimary) (uint64, error)
GetValidatorDashboardGroupExists(ctx context.Context, dashboardId t.VDBIdPrimary, groupId uint64) (bool, error)
- GetValidatorDashboardExistingValidatorCount(ctx context.Context, dashboardId t.VDBIdPrimary, validators []t.VDBValidator) (uint64, error)
AddValidatorDashboardValidators(ctx context.Context, dashboardId t.VDBIdPrimary, groupId uint64, validators []t.VDBValidator) ([]t.VDBPostValidatorsData, error)
AddValidatorDashboardValidatorsByDepositAddress(ctx context.Context, dashboardId t.VDBIdPrimary, groupId uint64, address string, limit uint64) ([]t.VDBPostValidatorsData, error)
AddValidatorDashboardValidatorsByWithdrawalAddress(ctx context.Context, dashboardId t.VDBIdPrimary, groupId uint64, address string, limit uint64) ([]t.VDBPostValidatorsData, error)
diff --git a/backend/pkg/api/data_access/vdb_management.go b/backend/pkg/api/data_access/vdb_management.go
index d415f0a23..b82bbff30 100644
--- a/backend/pkg/api/data_access/vdb_management.go
+++ b/backend/pkg/api/data_access/vdb_management.go
@@ -6,6 +6,7 @@ import (
"encoding/hex"
"fmt"
"math/big"
+ "slices"
"sort"
"strconv"
"strings"
@@ -15,7 +16,6 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/gobitfly/beaconchain/pkg/api/enums"
t "github.com/gobitfly/beaconchain/pkg/api/types"
- "github.com/gobitfly/beaconchain/pkg/commons/log"
"github.com/gobitfly/beaconchain/pkg/commons/utils"
constypes "github.com/gobitfly/beaconchain/pkg/consapi/types"
"github.com/lib/pq"
@@ -790,21 +790,6 @@ func (d *DataAccessService) GetValidatorDashboardGroupExists(ctx context.Context
return groupExists, err
}
-// return how many of the passed validators are already in the dashboard
-func (d *DataAccessService) GetValidatorDashboardExistingValidatorCount(ctx context.Context, dashboardId t.VDBIdPrimary, validators []t.VDBValidator) (uint64, error) {
- if len(validators) == 0 {
- return 0, nil
- }
-
- var count uint64
- err := d.alloyReader.GetContext(ctx, &count, `
- SELECT COUNT(*)
- FROM users_val_dashboards_validators
- WHERE dashboard_id = $1 AND validator_index = ANY($2)
- `, dashboardId, pq.Array(validators))
- return count, err
-}
-
func (d *DataAccessService) AddValidatorDashboardValidators(ctx context.Context, dashboardId t.VDBIdPrimary, groupId uint64, validators []t.VDBValidator) ([]t.VDBPostValidatorsData, error) {
if len(validators) == 0 {
// No validators to add
@@ -889,191 +874,145 @@ func (d *DataAccessService) AddValidatorDashboardValidators(ctx context.Context,
return result, nil
}
+// Updates the group for validators already in the dashboard linked to the deposit address.
+// Adds up to limit new validators associated with the deposit address, if not already in the dashboard.
func (d *DataAccessService) AddValidatorDashboardValidatorsByDepositAddress(ctx context.Context, dashboardId t.VDBIdPrimary, groupId uint64, address string, limit uint64) ([]t.VDBPostValidatorsData, error) {
- // for all validators already in the dashboard that are associated with the deposit address, update the group
- // then add no more than `limit` validators associated with the deposit address to the dashboard
addressParsed, err := hex.DecodeString(strings.TrimPrefix(address, "0x"))
if err != nil {
return nil, err
}
- if len(addressParsed) != 20 {
- return nil, fmt.Errorf("invalid deposit address: %s", address)
- }
- var validatorIndicesToAdd []uint64
- err = d.readerDb.SelectContext(ctx, &validatorIndicesToAdd, "SELECT validatorindex FROM validators WHERE pubkey IN (SELECT publickey FROM eth1_deposits WHERE from_address = $1) ORDER BY validatorindex LIMIT $2;", addressParsed, limit)
- if err != nil {
- return nil, err
- }
+ g, gCtx := errgroup.WithContext(ctx)
- // retrieve the existing validators
- var existingValidators []uint64
- err = d.alloyWriter.SelectContext(ctx, &existingValidators, "SELECT validator_index FROM users_val_dashboards_validators WHERE dashboard_id = $1", dashboardId)
- if err != nil {
- return nil, err
- }
- existingValidatorsMap := make(map[uint64]bool, len(existingValidators))
- for _, validatorIndex := range existingValidators {
- existingValidatorsMap[validatorIndex] = true
- }
-
- // filter out the validators that are already in the dashboard
+ // fetch validators that are already in the dashboard and associated with the deposit address
var validatorIndicesToUpdate []uint64
+
+ g.Go(func() error {
+ return d.readerDb.SelectContext(gCtx, &validatorIndicesToUpdate, `
+ SELECT DISTINCT uvdv.validator_index
+ FROM validators v
+ JOIN eth1_deposits d ON v.pubkey = d.publickey
+ JOIN users_val_dashboards_validators uvdv ON v.validatorindex = uvdv.validator_index
+ WHERE uvdv.dashboard_id = $1 AND d.from_address = $2;
+ `, dashboardId, addressParsed)
+ })
+
+ // fetch validators that are not yet in the dashboard and associated with the deposit address, up to the limit
var validatorIndicesToInsert []uint64
- for _, validatorIndex := range validatorIndicesToAdd {
- if _, ok := existingValidatorsMap[validatorIndex]; ok {
- validatorIndicesToUpdate = append(validatorIndicesToUpdate, validatorIndex)
- } else {
- validatorIndicesToInsert = append(validatorIndicesToInsert, validatorIndex)
- }
- }
+ g.Go(func() error {
+ return d.readerDb.SelectContext(gCtx, &validatorIndicesToInsert, `
+ SELECT DISTINCT v.validatorindex
+ FROM validators v
+ JOIN eth1_deposits d ON v.pubkey = d.publickey
+ LEFT JOIN users_val_dashboards_validators uvdv ON v.validatorindex = uvdv.validator_index AND uvdv.dashboard_id = $1
+ WHERE d.from_address = $2 AND uvdv.validator_index IS NULL
+ ORDER BY v.validatorindex
+ LIMIT $3;
+ `, dashboardId, addressParsed, limit)
+ })
- // update the group for all existing validators
- validatorIndices := make([]uint64, 0, int(limit))
- validatorIndices = append(validatorIndices, validatorIndicesToUpdate...)
-
- // insert the new validators up to the allowed user max limit taking into account how many validators are already in the dashboard
- if len(validatorIndicesToInsert) > 0 {
- freeSpace := int(limit) - len(existingValidators)
- if freeSpace > 0 {
- if len(validatorIndicesToInsert) > freeSpace { // cap inserts to the amount of free space available
- log.Infof("limiting the number of validators to insert to %d", freeSpace)
- validatorIndicesToInsert = validatorIndicesToInsert[:freeSpace]
- }
- validatorIndices = append(validatorIndices, validatorIndicesToInsert...)
- }
+ err = g.Wait()
+ if err != nil {
+ return nil, err
}
- if len(validatorIndices) == 0 {
- // no validators to add
- return []t.VDBPostValidatorsData{}, nil
- }
- log.Infof("inserting %d new validators and updating %d validators of dashboard %d, limit is %d", len(validatorIndicesToInsert), len(validatorIndicesToUpdate), dashboardId, limit)
+ validatorIndices := slices.Concat(validatorIndicesToUpdate, validatorIndicesToInsert)
+
return d.AddValidatorDashboardValidators(ctx, dashboardId, groupId, validatorIndices)
}
+// Updates the group for validators already in the dashboard linked to the withdrawal address.
+// Adds up to limit new validators associated with the withdrawal address, if not already in the dashboard.
func (d *DataAccessService) AddValidatorDashboardValidatorsByWithdrawalAddress(ctx context.Context, dashboardId t.VDBIdPrimary, groupId uint64, address string, limit uint64) ([]t.VDBPostValidatorsData, error) {
- // for all validators already in the dashboard that are associated with the withdrawal address, update the group
- // then add no more than `limit` validators associated with the deposit address to the dashboard
addressParsed, err := hex.DecodeString(strings.TrimPrefix(address, "0x"))
if err != nil {
return nil, err
}
- var validatorIndicesToAdd []uint64
- err = d.readerDb.SelectContext(ctx, &validatorIndicesToAdd, "SELECT validatorindex FROM validators WHERE withdrawalcredentials = $1 ORDER BY validatorindex LIMIT $2;", addressParsed, limit)
- if err != nil {
- return nil, err
- }
- // retrieve the existing validators
- var existingValidators []uint64
- err = d.alloyWriter.SelectContext(ctx, &existingValidators, "SELECT validator_index FROM users_val_dashboards_validators WHERE dashboard_id = $1", dashboardId)
- if err != nil {
- return nil, err
- }
- existingValidatorsMap := make(map[uint64]bool, len(existingValidators))
- for _, validatorIndex := range existingValidators {
- existingValidatorsMap[validatorIndex] = true
- }
+ g, gCtx := errgroup.WithContext(ctx)
- // filter out the validators that are already in the dashboard
+ // fetch validators that are already in the dashboard and associated with the withdrawal address
var validatorIndicesToUpdate []uint64
+ g.Go(func() error {
+ return d.readerDb.SelectContext(gCtx, &validatorIndicesToUpdate, `
+ SELECT DISTINCT uvdv.validator_index
+ FROM validators v
+ JOIN users_val_dashboards_validators uvdv ON v.validatorindex = uvdv.validator_index
+ WHERE uvdv.dashboard_id = $1 AND v.withdrawalcredentials = $2 AND uvdv.dashboard_id = $2;
+ `, dashboardId, addressParsed)
+ })
+
+ // fetch validators that are not yet in the dashboard and associated with the withdrawal address, up to the limit
var validatorIndicesToInsert []uint64
- for _, validatorIndex := range validatorIndicesToAdd {
- if _, ok := existingValidatorsMap[validatorIndex]; ok {
- validatorIndicesToUpdate = append(validatorIndicesToUpdate, validatorIndex)
- } else {
- validatorIndicesToInsert = append(validatorIndicesToInsert, validatorIndex)
- }
- }
+ g.Go(func() error {
+ return d.readerDb.SelectContext(gCtx, &validatorIndicesToInsert, `
+ SELECT DISTINCT v.validatorindex
+ FROM validators v
+ LEFT JOIN users_val_dashboards_validators uvdv ON v.validatorindex = uvdv.validator_index AND uvdv.dashboard_id = $1
+ WHERE v.withdrawalcredentials = $2 AND uvdv.validator_index IS NULL
+ ORDER BY v.validatorindex
+ LIMIT $3;
+ `, dashboardId, addressParsed, limit)
+ })
- // update the group for all existing validators
- validatorIndices := make([]uint64, 0, int(limit))
- validatorIndices = append(validatorIndices, validatorIndicesToUpdate...)
-
- // insert the new validators up to the allowed user max limit taking into account how many validators are already in the dashboard
- if len(validatorIndicesToInsert) > 0 {
- freeSpace := int(limit) - len(existingValidators)
- if freeSpace > 0 {
- if len(validatorIndicesToInsert) > freeSpace { // cap inserts to the amount of free space available
- log.Infof("limiting the number of validators to insert to %d", freeSpace)
- validatorIndicesToInsert = validatorIndicesToInsert[:freeSpace]
- }
- validatorIndices = append(validatorIndices, validatorIndicesToInsert...)
- }
+ err = g.Wait()
+ if err != nil {
+ return nil, err
}
- if len(validatorIndices) == 0 {
- // no validators to add
- return []t.VDBPostValidatorsData{}, nil
- }
- log.Infof("inserting %d new validators and updating %d validators of dashboard %d, limit is %d", len(validatorIndicesToInsert), len(validatorIndicesToUpdate), dashboardId, limit)
+ validatorIndices := slices.Concat(validatorIndicesToUpdate, validatorIndicesToInsert)
+
return d.AddValidatorDashboardValidators(ctx, dashboardId, groupId, validatorIndices)
}
+// Update the group for validators already in the dashboard linked to the graffiti (via produced block).
+// Add up to limit new validators associated with the graffiti, if not already in the dashboard.
func (d *DataAccessService) AddValidatorDashboardValidatorsByGraffiti(ctx context.Context, dashboardId t.VDBIdPrimary, groupId uint64, graffiti string, limit uint64) ([]t.VDBPostValidatorsData, error) {
- // for all validators already in the dashboard that are associated with the graffiti (by produced block), update the group
- // then add no more than `limit` validators associated with the deposit address to the dashboard
- var validatorIndicesToAdd []uint64
- err := d.readerDb.SelectContext(ctx, &validatorIndicesToAdd, "SELECT DISTINCT proposer FROM blocks WHERE graffiti_text = $1 ORDER BY proposer LIMIT $2;", graffiti, limit)
- if err != nil {
- return nil, err
- }
+ g, gCtx := errgroup.WithContext(ctx)
- // retrieve the existing validators
- var existingValidators []uint64
- err = d.alloyWriter.SelectContext(ctx, &existingValidators, "SELECT validator_index FROM users_val_dashboards_validators WHERE dashboard_id = $1", dashboardId)
- if err != nil {
- return nil, err
- }
- existingValidatorsMap := make(map[uint64]bool, len(existingValidators))
- for _, validatorIndex := range existingValidators {
- existingValidatorsMap[validatorIndex] = true
- }
-
- // filter out the validators that are already in the dashboard
+ // fetch validators that are already in the dashboard and associated with the graffiti
var validatorIndicesToUpdate []uint64
+ g.Go(func() error {
+ return d.readerDb.SelectContext(gCtx, &validatorIndicesToUpdate, `
+ SELECT DISTINCT uvdv.validator_index
+ FROM blocks b
+ JOIN users_val_dashboards_validators uvdv ON b.proposer = uvdv.validator_index
+ WHERE uvdv.dashboard_id = $1 AND b.graffiti_text = $2;
+ `, dashboardId, graffiti)
+ })
+
+ // fetch validators that are not yet in the dashboard and associated with the graffiti, up to the limit
var validatorIndicesToInsert []uint64
- for _, validatorIndex := range validatorIndicesToAdd {
- if _, ok := existingValidatorsMap[validatorIndex]; ok {
- validatorIndicesToUpdate = append(validatorIndicesToUpdate, validatorIndex)
- } else {
- validatorIndicesToInsert = append(validatorIndicesToInsert, validatorIndex)
- }
- }
+ g.Go(func() error {
+ return d.readerDb.SelectContext(gCtx, &validatorIndicesToInsert, `
+ SELECT DISTINCT b.proposer
+ FROM blocks b
+ LEFT JOIN users_val_dashboards_validators uvdv ON b.proposer = uvdv.validator_index AND uvdv.dashboard_id = $1
+ WHERE b.graffiti_text = $2 AND uvdv.validator_index IS NULL
+ ORDER BY b.proposer
+ LIMIT $3;
+ `, dashboardId, graffiti, limit)
+ })
- // update the group for all existing validators
- validatorIndices := make([]uint64, 0, int(limit))
- validatorIndices = append(validatorIndices, validatorIndicesToUpdate...)
-
- // insert the new validators up to the allowed user max limit taking into account how many validators are already in the dashboard
- if len(validatorIndicesToInsert) > 0 {
- freeSpace := int(limit) - len(existingValidators)
- if freeSpace > 0 {
- if len(validatorIndicesToInsert) > freeSpace { // cap inserts to the amount of free space available
- log.Infof("limiting the number of validators to insert to %d", freeSpace)
- validatorIndicesToInsert = validatorIndicesToInsert[:freeSpace]
- }
- validatorIndices = append(validatorIndices, validatorIndicesToInsert...)
- }
+ err := g.Wait()
+ if err != nil {
+ return nil, err
}
- if len(validatorIndices) == 0 {
- // no validators to add
- return []t.VDBPostValidatorsData{}, nil
- }
- log.Infof("inserting %d new validators and updating %d validators of dashboard %d, limit is %d", len(validatorIndicesToInsert), len(validatorIndicesToUpdate), dashboardId, limit)
+ validatorIndices := slices.Concat(validatorIndicesToUpdate, validatorIndicesToInsert)
+
return d.AddValidatorDashboardValidators(ctx, dashboardId, groupId, validatorIndices)
}
func (d *DataAccessService) RemoveValidatorDashboardValidators(ctx context.Context, dashboardId t.VDBIdPrimary, validators []t.VDBValidator) error {
if len(validators) == 0 {
- // // Remove all validators for the dashboard
- // _, err := d.alloyWriter.ExecContext(ctx, `
- // DELETE FROM users_val_dashboards_validators
- // WHERE dashboard_id = $1
- // `, dashboardId)
- return fmt.Errorf("calling RemoveValidatorDashboardValidators with empty validators list is not allowed")
+ // Remove all validators for the dashboard
+ // This is usually forbidden by API validation
+ _, err := d.alloyWriter.ExecContext(ctx, `
+ DELETE FROM users_val_dashboards_validators
+ WHERE dashboard_id = $1
+ `, dashboardId)
+ return err
}
//Create the query to delete validators
diff --git a/backend/pkg/api/handlers/public.go b/backend/pkg/api/handlers/public.go
index a2a92171a..ea62fb09d 100644
--- a/backend/pkg/api/handlers/public.go
+++ b/backend/pkg/api/handlers/public.go
@@ -6,7 +6,6 @@ import (
"fmt"
"math"
"net/http"
- "reflect"
"time"
"github.com/gobitfly/beaconchain/pkg/api/enums"
@@ -490,16 +489,15 @@ func (h *HandlerService) PublicDeleteValidatorDashboardGroup(w http.ResponseWrit
// PublicGetValidatorDashboardGroups godoc
//
-// @Description Add new validators to a specified dashboard or update the group of already-added validators.
+// @Description Add new validators to a specified dashboard or update the group of already-added validators. This endpoint will always add as many validators as possible, even if more validators are provided than allowed by the subscription plan. The response will contain a list of added validators.
// @Security ApiKeyInHeader || ApiKeyInQuery
// @Tags Validator Dashboard Management
// @Accept json
// @Produce json
// @Param dashboard_id path string true "The ID of the dashboard."
-// @Param request body handlers.PublicPostValidatorDashboardValidators.request true "`group_id`: (optional) Provide a single group id, to which all validators get added to. If omitted, the default group will be used.
To add validators, only one of the following fields can be set:
`validators`: Provide a list of validator indices or public keys to add to the dashboard.
`deposit_address`: (limited to subscription tiers with 'Bulk adding') Provide a deposit address from which as many validators as possible will be added to the dashboard.
`withdrawal_address`: (limited to subscription tiers with 'Bulk adding') Provide a withdrawal address from which as many validators as possible will be added to the dashboard.
`graffiti`: (limited to subscription tiers with 'Bulk adding') Provide a graffiti string from which as many validators as possible will be added to the dashboard.
"
+// @Param request body handlers.PublicPostValidatorDashboardValidators.request true "`group_id`: (optional) Provide a single group id, to which all validators get added to. If omitted, the default group will be used.
To add validators or update their group, only one of the following fields can be set:
`validators`: Provide a list of validator indices or public keys.
`deposit_address`: (limited to subscription tiers with 'Bulk adding') Provide a deposit address from which as many validators as possible will be added to the dashboard.
`withdrawal_address`: (limited to subscription tiers with 'Bulk adding') Provide a withdrawal address from which as many validators as possible will be added to the dashboard.
`graffiti`: (limited to subscription tiers with 'Bulk adding') Provide a graffiti string from which as many validators as possible will be added to the dashboard.
"
// @Success 201 {object} types.ApiDataResponse[[]types.VDBPostValidatorsData] "Returns a list of added validators."
// @Failure 400 {object} types.ApiErrorResponse
-// @Failure 409 {object} types.ApiErrorResponse "Conflict. The request could not be performed by the server because the authenticated user has already reached their validator limit."
// @Router /validator-dashboards/{dashboard_id}/validators [post]
func (h *HandlerService) PublicPostValidatorDashboardValidators(w http.ResponseWriter, r *http.Request) {
var v validationError
@@ -511,7 +509,9 @@ func (h *HandlerService) PublicPostValidatorDashboardValidators(w http.ResponseW
WithdrawalAddress string `json:"withdrawal_address,omitempty"`
Graffiti string `json:"graffiti,omitempty"`
}
- var req request
+ req := request{
+ GroupId: types.DefaultGroupId, // default value
+ }
if err := v.checkBody(&req, r); err != nil {
handleErr(w, r, err)
return
@@ -520,11 +520,17 @@ func (h *HandlerService) PublicPostValidatorDashboardValidators(w http.ResponseW
handleErr(w, r, v)
return
}
+ groupId := req.GroupId
// check if exactly one of validators, deposit_address, withdrawal_address, graffiti is set
- fields := []interface{}{req.Validators, req.DepositAddress, req.WithdrawalAddress, req.Graffiti}
+ nilFields := []bool{
+ req.Validators == nil,
+ req.DepositAddress == "",
+ req.WithdrawalAddress == "",
+ req.Graffiti == "",
+ }
var count int
- for _, set := range fields {
- if !reflect.ValueOf(set).IsZero() {
+ for _, isNil := range nilFields {
+ if !isNil {
count++
}
}
@@ -536,7 +542,6 @@ func (h *HandlerService) PublicPostValidatorDashboardValidators(w http.ResponseW
return
}
- groupId := req.GroupId
ctx := r.Context()
groupExists, err := h.dai.GetValidatorDashboardGroupExists(ctx, dashboardId, groupId)
if err != nil {
@@ -557,11 +562,23 @@ func (h *HandlerService) PublicPostValidatorDashboardValidators(w http.ResponseW
handleErr(w, r, err)
return
}
- limit := userInfo.PremiumPerks.ValidatorsPerDashboard
if req.Validators == nil && !userInfo.PremiumPerks.BulkAdding && !isUserAdmin(userInfo) {
- returnConflict(w, r, errors.New("bulk adding not allowed with current subscription plan"))
+ returnForbidden(w, r, errors.New("bulk adding not allowed with current subscription plan"))
+ return
+ }
+ dashboardLimit := userInfo.PremiumPerks.ValidatorsPerDashboard
+ existingValidatorCount, err := h.dai.GetValidatorDashboardValidatorsCount(ctx, dashboardId)
+ if err != nil {
+ handleErr(w, r, err)
return
}
+ var limit uint64
+ if isUserAdmin(userInfo) {
+ limit = math.MaxUint32 // no limit for admins
+ } else if dashboardLimit >= existingValidatorCount {
+ limit = dashboardLimit - existingValidatorCount
+ }
+
var data []types.VDBPostValidatorsData
var dataErr error
switch {
@@ -576,15 +593,8 @@ func (h *HandlerService) PublicPostValidatorDashboardValidators(w http.ResponseW
handleErr(w, r, err)
return
}
- // check if adding more validators than allowed
- existingValidatorCount, err := h.dai.GetValidatorDashboardExistingValidatorCount(ctx, dashboardId, validators)
- if err != nil {
- handleErr(w, r, err)
- return
- }
- if uint64(len(validators)) > existingValidatorCount+limit {
- returnConflict(w, r, fmt.Errorf("adding more validators than allowed, limit is %v new validators", limit))
- return
+ if len(validators) > int(limit) {
+ validators = validators[:limit]
}
data, dataErr = h.dai.AddValidatorDashboardValidators(ctx, dashboardId, groupId, validators)
@@ -687,7 +697,7 @@ func (h *HandlerService) PublicDeleteValidatorDashboardValidators(w http.Respons
handleErr(w, r, err)
return
}
- indices, publicKeys := v.checkValidators(req.Validators, false)
+ indices, publicKeys := v.checkValidators(req.Validators, forbidEmpty)
if v.hasErrors() {
handleErr(w, r, v)
return
From f2119f9fc9c3aa6d5db69499d175a9bfb7a8af80 Mon Sep 17 00:00:00 2001
From: benji-bitfly
Date: Wed, 25 Sep 2024 13:22:07 +0200
Subject: [PATCH 67/94] feat(notifications): add `dynamic` subscription
counter
Replace `hard-coded` subscruption counter with dynmic subscription counter in the notifications-table footer.
See: BEDS-478
---
.../components/notifications/DashboardsTable.vue | 16 +++++++---------
.../notifications/NotificationsClientsTable.vue | 3 ++-
.../notifications/NotificationsNetworkTable.vue | 4 ++--
3 files changed, 11 insertions(+), 12 deletions(-)
diff --git a/frontend/components/notifications/DashboardsTable.vue b/frontend/components/notifications/DashboardsTable.vue
index 62f221144..ef6ef6143 100644
--- a/frontend/components/notifications/DashboardsTable.vue
+++ b/frontend/components/notifications/DashboardsTable.vue
@@ -45,6 +45,7 @@ const openDialog = () => {
}
const getDashboardType = (isAccount: boolean): DashboardType => isAccount ? 'account' : 'validator'
+const { overview } = useNotificationsDashboardOverviewStore()
@@ -222,21 +223,18 @@ const getDashboardType = (isAccount: boolean): DashboardType => isAccount ? 'acc
@open-dialog="$emit('openDialog')"
/>
-
{{
$t(
"notifications.dashboards.footer.subscriptions.validators_shortened",
- { count: 1 },
- )
+ { count: overview?.vdb_subscriptions_count })
}}
|
{{
$t(
"notifications.dashboards.footer.subscriptions.accounts_shortened",
- { count: 1 },
- )
+ { count: overview?.adb_subscriptions_count })
}}
@@ -244,16 +242,16 @@ const getDashboardType = (isAccount: boolean): DashboardType => isAccount ? 'acc
{{
$t(
"notifications.dashboards.footer.subscriptions.validators",
- { count: 1 },
- )
+ { count: overview?.vdb_subscriptions_count })
+
}}