diff --git a/frontend/public/mock/notifications/managementDashboard.json b/frontend/public/mock/notifications/managementDashboard.json
deleted file mode 100644
index eb0d14c72..000000000
--- a/frontend/public/mock/notifications/managementDashboard.json
+++ /dev/null
@@ -1,31 +0,0 @@
-{
- "paging": {
- "total_count": 999
- },
- "data": [
- {
- "group_id": 123,
- "dashboard_id": 222,
- "dashboard_name": "My test dashboard",
- "dashboard_type": "validator",
- "subscriptions": ["missed_attestation"],
- "webhook": {
- "url": "",
- "via_discord": false
- },
- "networks": [1]
- },
- {
- "group_id": 123,
- "dashboard_id": 222,
- "dashboard_name": "My account dashboard",
- "dashboard_type": "account",
- "subscriptions": ["missed_attestation", "proposed_attestation"],
- "webhook": {
- "url": "https://discord.com/some-webhook-link",
- "via_discord": false
- },
- "networks": [1, 10, 42161, 8453]
- }
- ]
-}
diff --git a/frontend/stores/notifications/useNotificationsClientsStore.ts b/frontend/stores/notifications/useNotificationsClientsStore.ts
new file mode 100644
index 000000000..d5dbe9a9d
--- /dev/null
+++ b/frontend/stores/notifications/useNotificationsClientsStore.ts
@@ -0,0 +1,69 @@
+import { defineStore } from 'pinia'
+import type { InternalGetUserNotificationClientsResponse } from '~/types/api/notifications'
+import { API_PATH } from '~/types/customFetch'
+import type { TableQueryParams } from '~/types/datatable'
+
+const notificationsClientStore = defineStore('notifications-clients-store', () => {
+ const data = ref()
+ return { data }
+})
+
+export function useNotificationsClientStore() {
+ const { isLoggedIn } = useUserStore()
+
+ const { fetch } = useCustomFetch()
+ const { data } = storeToRefs(notificationsClientStore())
+ const {
+ cursor, isStoredQuery, onSort, pageSize, pendingQuery, query, setCursor, setPageSize, setSearch, setStoredQuery,
+ } = useTableQuery({
+ limit: 10, sort: 'timestamp:desc',
+ }, 10)
+ const isLoading = ref(false)
+
+ async function loadClientsNotifications(q: TableQueryParams) {
+ isLoading.value = true
+ setStoredQuery(q)
+ try {
+ const result = await fetch(
+ API_PATH.NOTIFICATIONS_CLIENTS,
+ undefined,
+ undefined,
+ q,
+ )
+
+ isLoading.value = false
+ if (!isStoredQuery(q)) {
+ return // in case some query params change while loading
+ }
+
+ data.value = result
+ }
+ catch (e) {
+ data.value = undefined
+ isLoading.value = false
+ }
+ return data.value
+ }
+
+ const clientsNotifications = computed(() => {
+ return data.value
+ })
+
+ watch(query, (q) => {
+ if (q) {
+ isLoggedIn.value && loadClientsNotifications(q)
+ }
+ }, { immediate: true })
+
+ return {
+ clientsNotifications,
+ cursor,
+ isLoading,
+ onSort,
+ pageSize,
+ query: pendingQuery,
+ setCursor,
+ setPageSize,
+ setSearch,
+ }
+}
diff --git a/frontend/types/customFetch.ts b/frontend/types/customFetch.ts
index dcc3ec474..18b2d3ea1 100644
--- a/frontend/types/customFetch.ts
+++ b/frontend/types/customFetch.ts
@@ -40,6 +40,7 @@ export enum API_PATH {
LATEST_STATE = '/latestState',
LOGIN = '/login',
LOGOUT = '/logout',
+ NOTIFICATIONS_CLIENTS = '/notifications/clients',
NOTIFICATIONS_DASHBOARDS = '/notifications/dashboards',
NOTIFICATIONS_MACHINE = '/notifications/machines',
NOTIFICATIONS_MANAGEMENT_GENERAL = '/notifications/managementGeneral',
@@ -279,6 +280,10 @@ export const mapping: Record = {
mock: false,
path: '/logout',
},
+ [API_PATH.NOTIFICATIONS_CLIENTS]: {
+ method: 'GET',
+ path: '/users/me/notifications/clients',
+ },
[API_PATH.NOTIFICATIONS_DASHBOARDS]: {
path: '/users/me/notifications/dashboards',
},
From 2c55bd2d88950cdfdd1242e4760ba469e05b1fad Mon Sep 17 00:00:00 2001
From: Patrick
Date: Mon, 16 Sep 2024 13:24:06 +0200
Subject: [PATCH 123/187] refactor(eth1indexer): update ens in seperate
go-routine (#858)
---
backend/cmd/eth1indexer/main.go | 25 +++++++++++++++++--------
1 file changed, 17 insertions(+), 8 deletions(-)
diff --git a/backend/cmd/eth1indexer/main.go b/backend/cmd/eth1indexer/main.go
index badcc7745..d6198b22a 100644
--- a/backend/cmd/eth1indexer/main.go
+++ b/backend/cmd/eth1indexer/main.go
@@ -189,6 +189,10 @@ func Run() {
}()
}
+ if *enableEnsUpdater {
+ go ImportEnsUpdatesLoop(bt, client, *ensBatchSize)
+ }
+
if *enableFullBalanceUpdater {
ProcessMetadataUpdates(bt, client, balanceUpdaterPrefix, *balanceUpdaterBatchSize, -1)
return
@@ -375,14 +379,6 @@ func Run() {
ProcessMetadataUpdates(bt, client, balanceUpdaterPrefix, *balanceUpdaterBatchSize, 10)
}
- if *enableEnsUpdater {
- err := bt.ImportEnsUpdates(client.GetNativeClient(), *ensBatchSize)
- if err != nil {
- log.Error(err, "error importing ens updates", 0, nil)
- continue
- }
- }
-
log.Infof("index run completed")
services.ReportStatus("eth1indexer", "Running", nil)
}
@@ -390,6 +386,19 @@ func Run() {
// utils.WaitForCtrlC()
}
+func ImportEnsUpdatesLoop(bt *db.Bigtable, client *rpc.ErigonClient, batchSize int64) {
+ time.Sleep(time.Second * 5)
+ for {
+ err := bt.ImportEnsUpdates(client.GetNativeClient(), batchSize)
+ if err != nil {
+ log.Error(err, "error importing ens updates", 0, nil)
+ } else {
+ services.ReportStatus("ensIndexer", "Running", nil)
+ }
+ time.Sleep(time.Second * 5)
+ }
+}
+
func UpdateTokenPrices(bt *db.Bigtable, client *rpc.ErigonClient, tokenListPath string) error {
tokenListContent, err := os.ReadFile(tokenListPath)
if err != nil {
From 83884000d790cb69e44b3231551967e0fa16e515 Mon Sep 17 00:00:00 2001
From: Manuel <5877862+manuelsc@users.noreply.github.com>
Date: Mon, 16 Sep 2024 14:39:16 +0200
Subject: [PATCH 124/187] BEDS-239: map apple product ids to internal ids
---
.../pkg/userservice/appsubscription_oracle.go | 22 ++++++++++++++++---
1 file changed, 19 insertions(+), 3 deletions(-)
diff --git a/backend/pkg/userservice/appsubscription_oracle.go b/backend/pkg/userservice/appsubscription_oracle.go
index 0b078674e..0f50ed916 100644
--- a/backend/pkg/userservice/appsubscription_oracle.go
+++ b/backend/pkg/userservice/appsubscription_oracle.go
@@ -233,13 +233,29 @@ func rejectReason(valid bool) string {
return "expired"
}
+// first 3 trillion dollar company and you can't reuse ids
+func mapAppleProductID(productID string) string {
+ mappings := map[string]string{
+ "orca.yearly.apple": "orca.yearly",
+ "orca.apple": "orca",
+ "dolphin.yearly.apple": "dolphin.yearly",
+ "dolphin.apple": "dolphin",
+ "guppy.yearly.apple": "guppy.yearly",
+ "guppy.apple": "guppy",
+ }
+ if mapped, ok := mappings[productID]; ok {
+ return mapped
+ }
+ return productID
+}
+
func verifyApple(apple *api.StoreClient, receipt *types.PremiumData) (*VerifyResponse, error) {
response := &VerifyResponse{
Valid: false,
ExpirationDate: 0,
RejectReason: "",
- ProductID: receipt.ProductID, // may be changed by this function to be different than receipt.ProductID
- Receipt: receipt.Receipt, // may be changed by this function to be different than receipt.Receipt
+ ProductID: mapAppleProductID(receipt.ProductID), // may be changed by this function to be different than receipt.ProductID
+ Receipt: receipt.Receipt, // may be changed by this function to be different than receipt.Receipt
}
if apple == nil {
@@ -300,7 +316,7 @@ func verifyApple(apple *api.StoreClient, receipt *types.PremiumData) (*VerifyRes
response.RejectReason = "invalid_product_id"
return response, nil
}
- response.ProductID = productId // update response to reflect the resolved product id
+ response.ProductID = mapAppleProductID(productId) // update response to reflect the resolved product id
expiresDateFloat, ok := claims["expiresDate"].(float64)
if !ok {
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 125/187] 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 e9cef55731f48062d232c67d094a8523fa70192e Mon Sep 17 00:00:00 2001
From: Lucca <109136188+LuccaBitfly@users.noreply.github.com>
Date: Tue, 17 Sep 2024 12:10:28 +0200
Subject: [PATCH 126/187] (BEDS-479) fix notification settings dummy generation
(#860)
---
backend/pkg/api/data_access/dummy.go | 4 ++--
backend/pkg/api/types/notifications.go | 4 ++--
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/backend/pkg/api/data_access/dummy.go b/backend/pkg/api/data_access/dummy.go
index 36247ea76..97bc9e369 100644
--- a/backend/pkg/api/data_access/dummy.go
+++ b/backend/pkg/api/data_access/dummy.go
@@ -43,9 +43,9 @@ func NewDummyService() *DummyService {
return &DummyService{}
}
-// generate random decimal.Decimal, should result in somewhere around 0.001 ETH (+/- a few decimal places) in Wei
+// generate random decimal.Decimal, result is between 0.001 and 1000 GWei (returned in Wei)
func randomEthDecimal() decimal.Decimal {
- decimal, _ := decimal.NewFromString(fmt.Sprintf("%d00000000000", rand.Int64N(10000000))) //nolint:gosec
+ decimal, _ := decimal.NewFromString(fmt.Sprintf("%d000000", rand.Int64N(1000000)+1)) //nolint:gosec
return decimal
}
diff --git a/backend/pkg/api/types/notifications.go b/backend/pkg/api/types/notifications.go
index 323f6ca00..f183988b1 100644
--- a/backend/pkg/api/types/notifications.go
+++ b/backend/pkg/api/types/notifications.go
@@ -141,8 +141,8 @@ type InternalGetUserNotificationNetworksResponse ApiPagingResponse[NotificationN
// ------------------------------------------------------------
// Notification Settings
type NotificationSettingsNetwork struct {
- GasAboveThreshold decimal.Decimal `json:"gas_above_threshold" faker:"boundary_start=0, boundary_end=1"` // 0 is disabled
- GasBelowThreshold decimal.Decimal `json:"gas_below_threshold" faker:"boundary_start=0, boundary_end=1"` // 0 is disabled
+ GasAboveThreshold decimal.Decimal `json:"gas_above_threshold" faker:"eth"` // 0 is disabled
+ GasBelowThreshold decimal.Decimal `json:"gas_below_threshold" faker:"eth"` // 0 is disabled
ParticipationRateThreshold float64 `json:"participation_rate_threshold" faker:"boundary_start=0, boundary_end=1"` // 0 is disabled
}
type NotificationNetwork struct {
From 54dc9593b1036cdeaf7bc04d1a18a9f40227e4a7 Mon Sep 17 00:00:00 2001
From: peter <1674920+peterbitfly@users.noreply.github.com>
Date: Tue, 17 Sep 2024 10:12:38 +0000
Subject: [PATCH 127/187] fix(dashboard): return network id in all cases
---
backend/pkg/api/api_test.go | 1 +
backend/pkg/api/data_access/vdb_management.go | 6 +++++-
2 files changed, 6 insertions(+), 1 deletion(-)
diff --git a/backend/pkg/api/api_test.go b/backend/pkg/api/api_test.go
index c422bf17f..15e4b8a64 100644
--- a/backend/pkg/api/api_test.go
+++ b/backend/pkg/api/api_test.go
@@ -389,6 +389,7 @@ func TestPublicAndSharedDashboards(t *testing.T) {
numValidators := resp.Data.Validators.Exited + resp.Data.Validators.Offline + resp.Data.Validators.Pending + resp.Data.Validators.Online + resp.Data.Validators.Slashed
assert.Greater(t, numValidators, uint64(0), "dashboard should contain at least one validator")
assert.Greater(t, len(resp.Data.Groups), 0, "dashboard should contain at least one group")
+ assert.Greater(t, resp.Data.Network, uint64(0), "dashboard should contain a network id greater than 0")
})
t.Run(fmt.Sprintf("[%s]: test group summary", dashboardId.id), func(t *testing.T) {
diff --git a/backend/pkg/api/data_access/vdb_management.go b/backend/pkg/api/data_access/vdb_management.go
index 9163a51ab..93b3d1f72 100644
--- a/backend/pkg/api/data_access/vdb_management.go
+++ b/backend/pkg/api/data_access/vdb_management.go
@@ -287,8 +287,10 @@ func (d *DataAccessService) GetValidatorDashboardOverview(ctx context.Context, d
id = $1`
return d.alloyReader.GetContext(ctx, &data.Network, query, dashboardId.Id)
})
+ } else { // load the chain id from the config in case of public dashboards
+ data.Network = utils.Config.Chain.ClConfig.DepositChainID
+ log.Info(utils.Config.Chain.ClConfig.DepositChainID)
}
- // TODO handle network of validator set dashboards
// Groups
if dashboardId.Validators == nil && !dashboardId.AggregateGroups {
@@ -514,6 +516,8 @@ func (d *DataAccessService) GetValidatorDashboardOverview(ctx context.Context, d
return nil, fmt.Errorf("error retrieving validator dashboard overview data: %v", err)
}
+ log.Info(data.Network)
+
return &data, nil
}
From 9bc77b239907e62186519efa99fd57dcad9139a3 Mon Sep 17 00:00:00 2001
From: peter <1674920+peterbitfly@users.noreply.github.com>
Date: Tue, 17 Sep 2024 10:20:49 +0000
Subject: [PATCH 128/187] chore(dashboard): remove unnecessary log statements
---
backend/pkg/api/data_access/vdb_management.go | 3 ---
1 file changed, 3 deletions(-)
diff --git a/backend/pkg/api/data_access/vdb_management.go b/backend/pkg/api/data_access/vdb_management.go
index 93b3d1f72..a96dced54 100644
--- a/backend/pkg/api/data_access/vdb_management.go
+++ b/backend/pkg/api/data_access/vdb_management.go
@@ -289,7 +289,6 @@ func (d *DataAccessService) GetValidatorDashboardOverview(ctx context.Context, d
})
} else { // load the chain id from the config in case of public dashboards
data.Network = utils.Config.Chain.ClConfig.DepositChainID
- log.Info(utils.Config.Chain.ClConfig.DepositChainID)
}
// Groups
@@ -516,8 +515,6 @@ func (d *DataAccessService) GetValidatorDashboardOverview(ctx context.Context, d
return nil, fmt.Errorf("error retrieving validator dashboard overview data: %v", err)
}
- log.Info(data.Network)
-
return &data, nil
}
From 3ff4be3781d99f302204338d2727a3235154e73e Mon Sep 17 00:00:00 2001
From: Patrick Pfeiffer
Date: Tue, 17 Sep 2024 13:41:50 +0200
Subject: [PATCH 129/187] 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 fa2d7bca5bce31d583d7ea8fbb8b9c2736fdee05 Mon Sep 17 00:00:00 2001
From: Lucca <109136188+LuccaBitfly@users.noreply.github.com>
Date: Tue, 17 Sep 2024 13:42:10 +0200
Subject: [PATCH 130/187] (BEDS-295) implement automated api doc generation
(#748)
---
.../workflows/backend-integration-test.yml | 4 +-
backend/.gitignore | 3 +-
backend/Makefile | 2 +-
backend/cmd/typescript_converter/main.go | 2 +-
backend/go.mod | 6 +++
backend/go.sum | 13 +++++-
backend/pkg/api/api_test.go | 43 +++++++++++++++++++
backend/pkg/api/data_access/data_access.go | 12 +++---
backend/pkg/api/data_access/dummy.go | 6 +++
backend/pkg/api/data_access/ratelimit.go | 22 ++++++++++
backend/pkg/api/docs/static.go | 6 +++
backend/pkg/api/handlers/common.go | 3 +-
backend/pkg/api/handlers/internal.go | 27 ++++++++++--
backend/pkg/api/handlers/public.go | 43 ++++++++++++++++---
backend/pkg/api/router.go | 5 +++
backend/pkg/api/types/data_access.go | 1 -
backend/pkg/api/types/ratelimit.go | 10 +++++
backend/pkg/commons/ratelimit/ratelimit.go | 3 +-
backend/pkg/commons/utils/config.go | 1 +
frontend/types/api/ratelimit.ts | 14 ++++++
20 files changed, 200 insertions(+), 26 deletions(-)
create mode 100644 backend/pkg/api/data_access/ratelimit.go
create mode 100644 backend/pkg/api/docs/static.go
create mode 100644 backend/pkg/api/types/ratelimit.go
create mode 100644 frontend/types/api/ratelimit.ts
diff --git a/.github/workflows/backend-integration-test.yml b/.github/workflows/backend-integration-test.yml
index 48243480e..c95d43c70 100644
--- a/.github/workflows/backend-integration-test.yml
+++ b/.github/workflows/backend-integration-test.yml
@@ -33,7 +33,9 @@ jobs:
cache-dependency-path: 'backend/go.sum'
- name: Test with the Go CLI
working-directory: backend
- run: go test -failfast ./pkg/api/... -config "${{ secrets.CI_CONFIG_PATH }}"
+ run:
+ go install github.com/swaggo/swag/cmd/swag@latest && swag init --ot json -o ./pkg/api/docs -d ./pkg/api/ -g ./handlers/public.go
+ go test -failfast ./pkg/api/... -config "${{ secrets.CI_CONFIG_PATH }}"
diff --git a/backend/.gitignore b/backend/.gitignore
index 7006633d8..b5f10c4da 100644
--- a/backend/.gitignore
+++ b/backend/.gitignore
@@ -5,4 +5,5 @@ local_deployment/config.yml
local_deployment/elconfig.json
local_deployment/.env
__gitignore
-cmd/playground
\ No newline at end of file
+cmd/playground
+pkg/api/docs/swagger.json
diff --git a/backend/Makefile b/backend/Makefile
index 01c3705ba..dd099490d 100644
--- a/backend/Makefile
+++ b/backend/Makefile
@@ -10,8 +10,8 @@ CGO_CFLAGS_ALLOW="-O -D__BLST_PORTABLE__"
all:
mkdir -p bin
+ go install github.com/swaggo/swag/cmd/swag@latest && swag init --ot json -o ./pkg/api/docs -d ./pkg/api/ -g ./handlers/public.go
CGO_CFLAGS=${CGO_CFLAGS} CGO_CFLAGS_ALLOW=${CGO_CFLAGS_ALLOW} go build --ldflags=${LDFLAGS} -o ./bin/bc ./cmd/main.go
-
clean:
rm -rf bin
diff --git a/backend/cmd/typescript_converter/main.go b/backend/cmd/typescript_converter/main.go
index 360e57188..da6fa5be9 100644
--- a/backend/cmd/typescript_converter/main.go
+++ b/backend/cmd/typescript_converter/main.go
@@ -31,7 +31,7 @@ var typeMappings = map[string]string{
// Expects the following flags:
// -out: Output folder for the generated TypeScript file
-// Standard usage (execute in backend folder): go run cmd/typescript_converter/main.go -out ../frontend/types/api
+// Standard usage (execute in backend folder): go run cmd/main.go typescript-converter -out ../frontend/types/api
func Run() {
var out string
diff --git a/backend/go.mod b/backend/go.mod
index b53006df9..59fc22b83 100644
--- a/backend/go.mod
+++ b/backend/go.mod
@@ -24,6 +24,7 @@ require (
github.com/fergusstrange/embedded-postgres v1.29.0
github.com/gavv/httpexpect/v2 v2.16.0
github.com/go-faker/faker/v4 v4.3.0
+ github.com/go-openapi/spec v0.20.14
github.com/go-redis/redis/v8 v8.11.5
github.com/gobitfly/eth-rewards v0.1.2-0.20230403064929-411ddc40a5f7
github.com/gobitfly/eth.store v0.0.0-20240312111708-b43f13990280
@@ -139,6 +140,9 @@ require (
github.com/go-logr/logr v1.4.1 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
+ github.com/go-openapi/jsonpointer v0.20.2 // indirect
+ github.com/go-openapi/jsonreference v0.20.4 // indirect
+ github.com/go-openapi/swag v0.22.9 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/goccy/go-yaml v1.9.5 // indirect
@@ -178,6 +182,7 @@ require (
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/puddle/v2 v2.2.1 // indirect
github.com/jbenet/goprocess v0.1.4 // indirect
+ github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/libp2p/go-buffer-pool v0.1.0 // indirect
@@ -264,6 +269,7 @@ require (
lukechampine.com/blake3 v1.2.1 // indirect
moul.io/http2curl/v2 v2.3.0 // indirect
rsc.io/tmplfunc v0.0.3 // indirect
+ sigs.k8s.io/yaml v1.3.0 // indirect
)
replace github.com/wealdtech/go-merkletree v1.0.1-0.20190605192610-2bb163c2ea2a => github.com/rocket-pool/go-merkletree v1.0.1-0.20220406020931-c262d9b976dd
diff --git a/backend/go.sum b/backend/go.sum
index 7d41b5693..34c1e36af 100644
--- a/backend/go.sum
+++ b/backend/go.sum
@@ -285,6 +285,14 @@ github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
+github.com/go-openapi/jsonpointer v0.20.2 h1:mQc3nmndL8ZBzStEo3JYF8wzmeWffDH4VbXz58sAx6Q=
+github.com/go-openapi/jsonpointer v0.20.2/go.mod h1:bHen+N0u1KEO3YlmqOjTT9Adn1RfD91Ar825/PuiRVs=
+github.com/go-openapi/jsonreference v0.20.4 h1:bKlDxQxQJgwpUSgOENiMPzCTBVuc7vTdXSSgNeAhojU=
+github.com/go-openapi/jsonreference v0.20.4/go.mod h1:5pZJyJP2MnYCpoeoMAql78cCHauHj0V9Lhc506VOpw4=
+github.com/go-openapi/spec v0.20.14 h1:7CBlRnw+mtjFGlPDRZmAMnq35cRzI91xj03HVyUi/Do=
+github.com/go-openapi/spec v0.20.14/go.mod h1:8EOhTpBoFiask8rrgwbLC3zmJfz4zsCUueRuPM6GNkw=
+github.com/go-openapi/swag v0.22.9 h1:XX2DssF+mQKM2DHsbgZK74y/zj4mo9I99+89xUmuZCE=
+github.com/go-openapi/swag v0.22.9/go.mod h1:3/OXnFfnMAwBD099SwYRk7GD3xOrr1iL7d/XNLXVVwE=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
@@ -562,6 +570,7 @@ github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901 h1:rp+c0RAYOWj8
github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901/go.mod h1:Z86h9688Y0wesXCyonoVr47MasHilkuLMqGhRZ4Hpak=
github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4=
github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc=
+github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
@@ -1284,5 +1293,5 @@ rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU=
rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA=
sigs.k8s.io/structured-merge-diff/v3 v3.0.0 h1:dOmIZBMfhcHS09XZkMyUgkq5trg3/jRyJYFZUiaOp8E=
sigs.k8s.io/structured-merge-diff/v3 v3.0.0/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw=
-sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
-sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
+sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
+sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
diff --git a/backend/pkg/api/api_test.go b/backend/pkg/api/api_test.go
index c422bf17f..a38763240 100644
--- a/backend/pkg/api/api_test.go
+++ b/backend/pkg/api/api_test.go
@@ -9,12 +9,14 @@ import (
"net/http/httptest"
"os"
"os/exec"
+ "slices"
"sort"
"testing"
"time"
embeddedpostgres "github.com/fergusstrange/embedded-postgres"
"github.com/gavv/httpexpect/v2"
+ "github.com/go-openapi/spec"
"github.com/gobitfly/beaconchain/pkg/api"
dataaccess "github.com/gobitfly/beaconchain/pkg/api/data_access"
api_types "github.com/gobitfly/beaconchain/pkg/api/types"
@@ -25,6 +27,7 @@ import (
"github.com/jmoiron/sqlx"
"github.com/pressly/goose/v3"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
"golang.org/x/crypto/bcrypt"
)
@@ -111,6 +114,16 @@ func setup() error {
return fmt.Errorf("error inserting user 2: %w", err)
}
+ // insert dummy api weight for testing
+ _, err = tempDb.Exec(`
+ INSERT INTO api_weights (bucket, endpoint, method, params, weight, valid_from)
+ VALUES ($1, $2, $3, $4, $5, TO_TIMESTAMP($6))`,
+ "default", "/api/v2/test-ratelimit", "GET", "", 2, time.Now().Unix(),
+ )
+ if err != nil {
+ return fmt.Errorf("error inserting api weight: %w", err)
+ }
+
cfg := &types.Config{}
err = utils.ReadConfig(cfg, *configPath)
if err != nil {
@@ -469,3 +482,33 @@ func TestPublicAndSharedDashboards(t *testing.T) {
})
}
}
+
+func TestApiDoc(t *testing.T) {
+ e := httpexpect.WithConfig(getExpectConfig(t, ts))
+
+ t.Run("test api doc json", func(t *testing.T) {
+ resp := spec.Swagger{}
+ e.GET("/api/v2/docs/swagger.json").
+ Expect().
+ Status(http.StatusOK).JSON().Decode(&resp)
+
+ assert.Equal(t, "/api/v2", resp.BasePath, "swagger base path should be '/api/v2'")
+ require.NotNil(t, 0, resp.Paths, "swagger paths should not nil")
+ assert.NotEqual(t, 0, len(resp.Paths.Paths), "swagger paths should not be empty")
+ assert.NotEqual(t, 0, len(resp.Definitions), "swagger definitions should not be empty")
+ assert.NotEqual(t, 0, len(resp.Host), "swagger host should not be empty")
+ })
+
+ t.Run("test api ratelimit weights endpoint", func(t *testing.T) {
+ resp := api_types.InternalGetRatelimitWeightsResponse{}
+ e.GET("/api/i/ratelimit-weights").
+ Expect().
+ Status(http.StatusOK).JSON().Decode(&resp)
+
+ assert.GreaterOrEqual(t, len(resp.Data), 1, "ratelimit weights should contain at least one entry")
+ testEndpointIndex := slices.IndexFunc(resp.Data, func(item api_types.ApiWeightItem) bool {
+ return item.Endpoint == "/api/v2/test-ratelimit"
+ })
+ assert.GreaterOrEqual(t, testEndpointIndex, 0, "ratelimit weights should contain an entry for /api/v2/test-ratelimit")
+ })
+}
diff --git a/backend/pkg/api/data_access/data_access.go b/backend/pkg/api/data_access/data_access.go
index c4b3b8338..3fc31d105 100644
--- a/backend/pkg/api/data_access/data_access.go
+++ b/backend/pkg/api/data_access/data_access.go
@@ -13,7 +13,6 @@ import (
"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/jmoiron/sqlx"
"github.com/pkg/errors"
)
@@ -29,6 +28,7 @@ type DataAccessor interface {
BlockRepository
ArchiverRepository
ProtocolRepository
+ RatelimitRepository
HealthzRepository
StartDataAccessServices()
@@ -203,7 +203,7 @@ func createDataAccessService(cfg *types.Config) *DataAccessService {
wg.Add(1)
go func() {
defer wg.Done()
- bt, err := db.InitBigtable(utils.Config.Bigtable.Project, utils.Config.Bigtable.Instance, fmt.Sprintf("%d", utils.Config.Chain.ClConfig.DepositChainID), utils.Config.RedisCacheEndpoint)
+ bt, err := db.InitBigtable(cfg.Bigtable.Project, cfg.Bigtable.Instance, fmt.Sprintf("%d", cfg.Chain.ClConfig.DepositChainID), cfg.RedisCacheEndpoint)
if err != nil {
log.Fatal(err, "error connecting to bigtable", 0)
}
@@ -211,11 +211,11 @@ func createDataAccessService(cfg *types.Config) *DataAccessService {
}()
// Initialize the tiered cache (redis)
- if utils.Config.TieredCacheProvider == "redis" || len(utils.Config.RedisCacheEndpoint) != 0 {
+ if cfg.TieredCacheProvider == "redis" || len(cfg.RedisCacheEndpoint) != 0 {
wg.Add(1)
go func() {
defer wg.Done()
- cache.MustInitTieredCache(utils.Config.RedisCacheEndpoint)
+ cache.MustInitTieredCache(cfg.RedisCacheEndpoint)
log.Infof("tiered Cache initialized, latest finalized epoch: %v", cache.LatestFinalizedEpoch.Get())
}()
}
@@ -225,7 +225,7 @@ func createDataAccessService(cfg *types.Config) *DataAccessService {
go func() {
defer wg.Done()
rdc := redis.NewClient(&redis.Options{
- Addr: utils.Config.RedisSessionStoreEndpoint,
+ Addr: cfg.RedisSessionStoreEndpoint,
ReadTimeout: time.Second * 60,
})
@@ -237,7 +237,7 @@ func createDataAccessService(cfg *types.Config) *DataAccessService {
wg.Wait()
- if utils.Config.TieredCacheProvider != "redis" {
+ if cfg.TieredCacheProvider != "redis" {
log.Fatal(fmt.Errorf("no cache provider set, please set TierdCacheProvider (example redis)"), "", 0)
}
diff --git a/backend/pkg/api/data_access/dummy.go b/backend/pkg/api/data_access/dummy.go
index 97bc9e369..4590a106b 100644
--- a/backend/pkg/api/data_access/dummy.go
+++ b/backend/pkg/api/data_access/dummy.go
@@ -636,6 +636,12 @@ func (d *DummyService) GetRocketPoolOverview(ctx context.Context) (*t.RocketPool
return getDummyStruct[t.RocketPoolData]()
}
+func (d *DummyService) GetApiWeights(ctx context.Context) ([]t.ApiWeightItem, error) {
+ r := []t.ApiWeightItem{}
+ err := commonFakeData(&r)
+ return r, err
+}
+
func (d *DummyService) GetHealthz(ctx context.Context, showAll bool) t.HealthzData {
r, _ := getDummyData[t.HealthzData]()
return r
diff --git a/backend/pkg/api/data_access/ratelimit.go b/backend/pkg/api/data_access/ratelimit.go
new file mode 100644
index 000000000..8c17c5d0f
--- /dev/null
+++ b/backend/pkg/api/data_access/ratelimit.go
@@ -0,0 +1,22 @@
+package dataaccess
+
+import (
+ "context"
+
+ "github.com/gobitfly/beaconchain/pkg/api/types"
+)
+
+type RatelimitRepository interface {
+ GetApiWeights(ctx context.Context) ([]types.ApiWeightItem, error)
+ // TODO @patrick: move queries from commons/ratelimit/ratelimit.go to here
+}
+
+func (d *DataAccessService) GetApiWeights(ctx context.Context) ([]types.ApiWeightItem, error) {
+ var result []types.ApiWeightItem
+ err := d.userReader.SelectContext(ctx, &result, `
+ SELECT bucket, endpoint, method, weight
+ FROM api_weights
+ WHERE valid_from <= NOW()
+ `)
+ return result, err
+}
diff --git a/backend/pkg/api/docs/static.go b/backend/pkg/api/docs/static.go
new file mode 100644
index 000000000..93087d82f
--- /dev/null
+++ b/backend/pkg/api/docs/static.go
@@ -0,0 +1,6 @@
+package docs
+
+import "embed"
+
+//go:embed *
+var Files embed.FS
diff --git a/backend/pkg/api/handlers/common.go b/backend/pkg/api/handlers/common.go
index ecc9794db..3a895c481 100644
--- a/backend/pkg/api/handlers/common.go
+++ b/backend/pkg/api/handlers/common.go
@@ -528,8 +528,7 @@ func checkEnum[T enums.EnumFactory[T]](v *validationError, enumString string, na
}
// checkEnumIsAllowed checks if the given enum is in the list of allowed enums.
-// precondition: the enum is the same type as the allowed enums.
-func (v *validationError) checkEnumIsAllowed(enum enums.Enum, allowed []enums.Enum, name string) {
+func checkEnumIsAllowed[T enums.EnumFactory[T]](v *validationError, enum T, allowed []T, name string) {
if enums.IsInvalidEnum(enum) {
v.add(name, "parameter is missing or invalid, please check the API documentation")
return
diff --git a/backend/pkg/api/handlers/internal.go b/backend/pkg/api/handlers/internal.go
index d111ab7ca..be1d96ecb 100644
--- a/backend/pkg/api/handlers/internal.go
+++ b/backend/pkg/api/handlers/internal.go
@@ -26,6 +26,21 @@ func (h *HandlerService) InternalGetProductSummary(w http.ResponseWriter, r *htt
returnOk(w, r, response)
}
+// --------------------------------------
+// API Ratelimit Weights
+
+func (h *HandlerService) InternalGetRatelimitWeights(w http.ResponseWriter, r *http.Request) {
+ data, err := h.dai.GetApiWeights(r.Context())
+ if err != nil {
+ handleErr(w, r, err)
+ return
+ }
+ response := types.InternalGetRatelimitWeightsResponse{
+ Data: data,
+ }
+ returnOk(w, r, response)
+}
+
// --------------------------------------
// Latest State
@@ -85,7 +100,7 @@ func (h *HandlerService) InternalPostAdConfigurations(w http.ResponseWriter, r *
handleErr(w, r, err)
return
}
- if user.UserGroup != "ADMIN" {
+ if user.UserGroup != types.UserGroupAdmin {
returnForbidden(w, r, errors.New("user is not an admin"))
return
}
@@ -131,7 +146,7 @@ func (h *HandlerService) InternalGetAdConfigurations(w http.ResponseWriter, r *h
handleErr(w, r, err)
return
}
- if user.UserGroup != "ADMIN" {
+ if user.UserGroup != types.UserGroupAdmin {
returnForbidden(w, r, errors.New("user is not an admin"))
return
}
@@ -161,7 +176,7 @@ func (h *HandlerService) InternalPutAdConfiguration(w http.ResponseWriter, r *ht
handleErr(w, r, err)
return
}
- if user.UserGroup != "ADMIN" {
+ if user.UserGroup != types.UserGroupAdmin {
returnForbidden(w, r, errors.New("user is not an admin"))
return
}
@@ -207,7 +222,7 @@ func (h *HandlerService) InternalDeleteAdConfiguration(w http.ResponseWriter, r
handleErr(w, r, err)
return
}
- if user.UserGroup != "ADMIN" {
+ if user.UserGroup != types.UserGroupAdmin {
returnForbidden(w, r, errors.New("user is not an admin"))
return
}
@@ -1311,3 +1326,7 @@ func (h *HandlerService) InternalGetSlotBlobs(w http.ResponseWriter, r *http.Req
}
returnOk(w, r, response)
}
+
+func (h *HandlerService) ReturnOk(w http.ResponseWriter, r *http.Request) {
+ returnOk(w, r, nil)
+}
diff --git a/backend/pkg/api/handlers/public.go b/backend/pkg/api/handlers/public.go
index 3e80c2769..a06e798c3 100644
--- a/backend/pkg/api/handlers/public.go
+++ b/backend/pkg/api/handlers/public.go
@@ -17,6 +17,25 @@ import (
// Public handlers may only be authenticated by an API key
// Public handlers must never call internal handlers
+// @title beaconcha.in API
+// @version 2.0
+// @description To authenticate your API request beaconcha.in uses API Keys. Set your API Key either by:
+// @description - Setting the `Authorization` header in the following format: `Authorization: Bearer `. (recommended)
+// @description - Setting the URL query parameter in the following format: `api_key={your_api_key}`.\
+// @description Example: `https://beaconcha.in/api/v2/example?field=value&api_key={your_api_key}`
+
+// @host beaconcha.in
+// @BasePath /api/v2
+
+// @securitydefinitions.apikey ApiKeyInHeader
+// @in header
+// @name Authorization
+// @description Use your API key as a Bearer token, e.g. `Bearer `
+
+// @securitydefinitions.apikey ApiKeyInQuery
+// @in query
+// @name api_key
+
func (h *HandlerService) PublicGetHealthz(w http.ResponseWriter, r *http.Request) {
var v validationError
showAll := v.checkBool(r.URL.Query().Get("show_all"), "show_all")
@@ -112,6 +131,18 @@ func (h *HandlerService) PublicPutAccountDashboardTransactionsSettings(w http.Re
returnOk(w, r, nil)
}
+// PublicPostValidatorDashboards godoc
+//
+// @Description Create a new validator dashboard. **Note**: New dashboards will automatically have a default group created.
+// @Security ApiKeyInHeader || ApiKeyInQuery
+// @Tags Validator Dashboards
+// @Accept json
+// @Produce json
+// @Param request body handlers.PublicPostValidatorDashboards.request true "`name`: Specify the name of the dashboard. `network`: Specify the network for the dashboard. Possible options are:
`ethereum`
`gnosis`
"
+// @Success 201 {object} types.ApiDataResponse[types.VDBPostReturnData]
+// @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 dashboard limit."
+// @Router /validator-dashboards [post]
func (h *HandlerService) PublicPostValidatorDashboards(w http.ResponseWriter, r *http.Request) {
var v validationError
userId, err := GetUserIdByContext(r)
@@ -792,8 +823,8 @@ func (h *HandlerService) PublicGetValidatorDashboardSummary(w http.ResponseWrite
period := checkEnum[enums.TimePeriod](&v, q.Get("period"), "period")
// allowed periods are: all_time, last_30d, last_7d, last_24h, last_1h
- allowedPeriods := []enums.Enum{enums.TimePeriods.AllTime, enums.TimePeriods.Last30d, enums.TimePeriods.Last7d, enums.TimePeriods.Last24h, enums.TimePeriods.Last1h}
- v.checkEnumIsAllowed(period, allowedPeriods, "period")
+ allowedPeriods := []enums.TimePeriod{enums.TimePeriods.AllTime, enums.TimePeriods.Last30d, enums.TimePeriods.Last7d, enums.TimePeriods.Last24h, enums.TimePeriods.Last1h}
+ checkEnumIsAllowed(&v, period, allowedPeriods, "period")
if v.hasErrors() {
handleErr(w, r, v)
return
@@ -828,8 +859,8 @@ func (h *HandlerService) PublicGetValidatorDashboardGroupSummary(w http.Response
groupId := v.checkGroupId(vars["group_id"], forbidEmpty)
period := checkEnum[enums.TimePeriod](&v, r.URL.Query().Get("period"), "period")
// allowed periods are: all_time, last_30d, last_7d, last_24h, last_1h
- allowedPeriods := []enums.Enum{enums.TimePeriods.AllTime, enums.TimePeriods.Last30d, enums.TimePeriods.Last7d, enums.TimePeriods.Last24h, enums.TimePeriods.Last1h}
- v.checkEnumIsAllowed(period, allowedPeriods, "period")
+ allowedPeriods := []enums.TimePeriod{enums.TimePeriods.AllTime, enums.TimePeriods.Last30d, enums.TimePeriods.Last7d, enums.TimePeriods.Last24h, enums.TimePeriods.Last1h}
+ checkEnumIsAllowed(&v, period, allowedPeriods, "period")
if v.hasErrors() {
handleErr(w, r, v)
return
@@ -897,8 +928,8 @@ func (h *HandlerService) PublicGetValidatorDashboardSummaryValidators(w http.Res
duty := checkEnum[enums.ValidatorDuty](&v, q.Get("duty"), "duty")
period := checkEnum[enums.TimePeriod](&v, q.Get("period"), "period")
// allowed periods are: all_time, last_30d, last_7d, last_24h, last_1h
- allowedPeriods := []enums.Enum{enums.TimePeriods.AllTime, enums.TimePeriods.Last30d, enums.TimePeriods.Last7d, enums.TimePeriods.Last24h, enums.TimePeriods.Last1h}
- v.checkEnumIsAllowed(period, allowedPeriods, "period")
+ allowedPeriods := []enums.TimePeriod{enums.TimePeriods.AllTime, enums.TimePeriods.Last30d, enums.TimePeriods.Last7d, enums.TimePeriods.Last24h, enums.TimePeriods.Last1h}
+ checkEnumIsAllowed(&v, period, allowedPeriods, "period")
if v.hasErrors() {
handleErr(w, r, v)
return
diff --git a/backend/pkg/api/router.go b/backend/pkg/api/router.go
index 0b3d6a819..5338b5ccd 100644
--- a/backend/pkg/api/router.go
+++ b/backend/pkg/api/router.go
@@ -5,6 +5,7 @@ import (
"regexp"
dataaccess "github.com/gobitfly/beaconchain/pkg/api/data_access"
+ "github.com/gobitfly/beaconchain/pkg/api/docs"
handlers "github.com/gobitfly/beaconchain/pkg/api/handlers"
"github.com/gobitfly/beaconchain/pkg/commons/log"
"github.com/gobitfly/beaconchain/pkg/commons/metrics"
@@ -39,6 +40,8 @@ func NewApiRouter(dataAccessor dataaccess.DataAccessor, cfg *types.Config) *mux.
addRoutes(handlerService, publicRouter, internalRouter, cfg)
+ // serve static files
+ publicRouter.PathPrefix("/docs/").Handler(http.StripPrefix("/api/v2/docs/", http.FileServer(http.FS(docs.Files))))
router.Use(metrics.HttpMiddleware)
return router
@@ -88,6 +91,8 @@ func addRoutes(hs *handlers.HandlerService, publicRouter, internalRouter *mux.Ro
{http.MethodGet, "/healthz", hs.PublicGetHealthz, nil},
{http.MethodGet, "/healthz-loadbalancer", hs.PublicGetHealthzLoadbalancer, nil},
+ {http.MethodGet, "/ratelimit-weights", nil, hs.InternalGetRatelimitWeights},
+
{http.MethodPost, "/login", nil, hs.InternalPostLogin},
{http.MethodGet, "/mobile/authorize", nil, hs.InternalPostMobileAuthorize},
diff --git a/backend/pkg/api/types/data_access.go b/backend/pkg/api/types/data_access.go
index 49d88b926..b24a6b76c 100644
--- a/backend/pkg/api/types/data_access.go
+++ b/backend/pkg/api/types/data_access.go
@@ -212,7 +212,6 @@ type VDBValidatorSummaryChartRow struct {
SyncScheduled float64 `db:"sync_scheduled"`
}
-// -------------------------
// healthz structs
type HealthzResult struct {
diff --git a/backend/pkg/api/types/ratelimit.go b/backend/pkg/api/types/ratelimit.go
new file mode 100644
index 000000000..6a1155096
--- /dev/null
+++ b/backend/pkg/api/types/ratelimit.go
@@ -0,0 +1,10 @@
+package types
+
+type ApiWeightItem struct {
+ Bucket string `db:"bucket"`
+ Endpoint string `db:"endpoint"`
+ Method string `db:"method"`
+ Weight int `db:"weight"`
+}
+
+type InternalGetRatelimitWeightsResponse ApiDataResponse[[]ApiWeightItem]
diff --git a/backend/pkg/commons/ratelimit/ratelimit.go b/backend/pkg/commons/ratelimit/ratelimit.go
index 8a79d9a75..a57900bba 100644
--- a/backend/pkg/commons/ratelimit/ratelimit.go
+++ b/backend/pkg/commons/ratelimit/ratelimit.go
@@ -56,6 +56,7 @@ const (
FallbackRateLimitSecond = 20 // RateLimit per second for when redis is offline
FallbackRateLimitBurst = 20 // RateLimit burst for when redis is offline
+ defaultWeight = 1 // if no weight is set for a route, use this one
defaultBucket = "default" // if no bucket is set for a route, use this one
statsTruncateDuration = time.Hour * 1 // ratelimit-stats are truncated to this duration
@@ -951,7 +952,7 @@ func getWeight(r *http.Request) (cost int64, identifier, bucket string) {
bucket, bucketOk := buckets[route]
weightsMu.RUnlock()
if !weightOk {
- weight = 1
+ weight = defaultWeight
}
if !bucketOk {
bucket = defaultBucket
diff --git a/backend/pkg/commons/utils/config.go b/backend/pkg/commons/utils/config.go
index 1cc3179eb..ad8529fd0 100644
--- a/backend/pkg/commons/utils/config.go
+++ b/backend/pkg/commons/utils/config.go
@@ -262,6 +262,7 @@ func ReadConfig(cfg *types.Config, path string) error {
"mainCurrency": cfg.Frontend.MainCurrency,
}, "did init config")
+ Config = cfg
return nil
}
diff --git a/frontend/types/api/ratelimit.ts b/frontend/types/api/ratelimit.ts
new file mode 100644
index 000000000..aec92ffb8
--- /dev/null
+++ b/frontend/types/api/ratelimit.ts
@@ -0,0 +1,14 @@
+// Code generated by tygo. DO NOT EDIT.
+/* eslint-disable */
+import type { ApiDataResponse } from './common'
+
+//////////
+// source: ratelimit.go
+
+export interface ApiWeightItem {
+ Bucket: string;
+ Endpoint: string;
+ Method: string;
+ Weight: number /* int */;
+}
+export type InternalGetRatelimitWeightsResponse = ApiDataResponse;
From 9fbcabc81242371e0e02ed107a935f0fc50f90fd Mon Sep 17 00:00:00 2001
From: Lucca <109136188+LuccaBitfly@users.noreply.github.com>
Date: Tue, 17 Sep 2024 14:21:43 +0200
Subject: [PATCH 131/187] BEDS 322/annotate endpoints (#820)
---
backend/pkg/api/data_access/user.go | 7 +-
backend/pkg/api/data_access/vdb_management.go | 8 +-
backend/pkg/api/handlers/common.go | 2 +-
backend/pkg/api/handlers/public.go | 456 +++++++++++++++++-
backend/pkg/api/types/common.go | 4 +-
backend/pkg/api/types/validator_dashboard.go | 34 +-
6 files changed, 469 insertions(+), 42 deletions(-)
diff --git a/backend/pkg/api/data_access/user.go b/backend/pkg/api/data_access/user.go
index 598f83995..c7c22d9cf 100644
--- a/backend/pkg/api/data_access/user.go
+++ b/backend/pkg/api/data_access/user.go
@@ -287,7 +287,10 @@ func (d *DataAccessService) GetUserInfo(ctx context.Context, userId uint64) (*t.
}{}
err = d.userReader.GetContext(ctx, &result, `SELECT email, COALESCE(user_group, '') as user_group FROM users WHERE id = $1`, userId)
if err != nil {
- return nil, fmt.Errorf("error getting userEmail for user %v: %w", userId, err)
+ if errors.Is(err, sql.ErrNoRows) {
+ return nil, fmt.Errorf("%w: user not found", ErrNotFound)
+ }
+ return nil, err
}
userInfo.Email = result.Email
userInfo.UserGroup = result.UserGroup
@@ -764,7 +767,7 @@ func (d *DataAccessService) GetUserDashboards(ctx context.Context, userId uint64
err := wg.Wait()
if err != nil {
- return nil, fmt.Errorf("error retrieving user dashboards data: %v", err)
+ return nil, fmt.Errorf("error retrieving user dashboards data: %w", err)
}
// Fill the result
diff --git a/backend/pkg/api/data_access/vdb_management.go b/backend/pkg/api/data_access/vdb_management.go
index 9163a51ab..b523103c6 100644
--- a/backend/pkg/api/data_access/vdb_management.go
+++ b/backend/pkg/api/data_access/vdb_management.go
@@ -148,7 +148,7 @@ func (d *DataAccessService) GetValidatorDashboardInfo(ctx context.Context, dashb
err := wg.Wait()
if err != nil {
- return nil, fmt.Errorf("error retrieving user dashboards data: %v", err)
+ return nil, fmt.Errorf("error retrieving user dashboards data: %w", err)
}
return result, nil
@@ -329,7 +329,7 @@ func (d *DataAccessService) GetValidatorDashboardOverview(ctx context.Context, d
validators, err := d.getDashboardValidators(ctx, dashboardId, nil)
if err != nil {
- return fmt.Errorf("error retrieving validators from dashboard id: %v", err)
+ return fmt.Errorf("error retrieving validators from dashboard id: %w", err)
}
if dashboardId.Validators != nil || dashboardId.AggregateGroups {
@@ -475,7 +475,7 @@ func (d *DataAccessService) GetValidatorDashboardOverview(ctx context.Context, d
query, args, err := ds.Prepared(true).ToSQL()
if err != nil {
- return fmt.Errorf("error preparing query: %v", err)
+ return fmt.Errorf("error preparing query: %w", err)
}
err = d.clickhouseReader.GetContext(ctx, &queryResult, query, args...)
@@ -511,7 +511,7 @@ func (d *DataAccessService) GetValidatorDashboardOverview(ctx context.Context, d
err = eg.Wait()
if err != nil {
- return nil, fmt.Errorf("error retrieving validator dashboard overview data: %v", err)
+ return nil, fmt.Errorf("error retrieving validator dashboard overview data: %w", err)
}
return &data, nil
diff --git a/backend/pkg/api/handlers/common.go b/backend/pkg/api/handlers/common.go
index 3a895c481..ca585724b 100644
--- a/backend/pkg/api/handlers/common.go
+++ b/backend/pkg/api/handlers/common.go
@@ -566,7 +566,7 @@ func checkSort[T enums.EnumFactory[T]](v *validationError, sortString string) *t
return nil
}
if len(sortSplit) == 1 {
- sortSplit = append(sortSplit, "")
+ sortSplit = append(sortSplit, ":asc")
}
sortCol := checkEnum[T](v, sortSplit[0], "sort")
order := v.parseSortOrder(sortSplit[1])
diff --git a/backend/pkg/api/handlers/public.go b/backend/pkg/api/handlers/public.go
index a06e798c3..95df23ea7 100644
--- a/backend/pkg/api/handlers/public.go
+++ b/backend/pkg/api/handlers/public.go
@@ -135,7 +135,7 @@ func (h *HandlerService) PublicPutAccountDashboardTransactionsSettings(w http.Re
//
// @Description Create a new validator dashboard. **Note**: New dashboards will automatically have a default group created.
// @Security ApiKeyInHeader || ApiKeyInQuery
-// @Tags Validator Dashboards
+// @Tags Validator Dashboard
// @Accept json
// @Produce json
// @Param request body handlers.PublicPostValidatorDashboards.request true "`name`: Specify the name of the dashboard. `network`: Specify the network for the dashboard. Possible options are:
`ethereum`
`gnosis`
"
@@ -193,6 +193,16 @@ func (h *HandlerService) PublicPostValidatorDashboards(w http.ResponseWriter, r
returnCreated(w, r, response)
}
+// PublicGetValidatorDashboards godoc
+//
+// @Description Get overview information for a specified dashboard
+// @Tags Validator Dashboard
+// @Produce json
+// @Param dashboard_id path string true "The ID of the dashboard."
+// @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.GetValidatorDashboardResponse
+// @Failure 400 {object} types.ApiErrorResponse "Bad Request"
+// @Router /validator-dashboards/{dashboard_id} [get]
func (h *HandlerService) PublicGetValidatorDashboard(w http.ResponseWriter, r *http.Request) {
var v validationError
dashboardIdParam := mux.Vars(r)["dashboard_id"]
@@ -244,6 +254,16 @@ func (h *HandlerService) PublicGetValidatorDashboard(w http.ResponseWriter, r *h
returnOk(w, r, response)
}
+// PublicPutValidatorDashboard godoc
+//
+// @Description Delete a specified validator dashboard.
+// @Security ApiKeyInHeader || ApiKeyInQuery
+// @Tags Validator Dashboard Management
+// @Produce json
+// @Param dashboard_id path string true "The ID of the dashboard."
+// @Success 204 "Dashboard deleted successfully."
+// @Failure 400 {object} types.ApiErrorResponse "Bad Request"
+// @Router /validator-dashboards/{dashboard_id} [delete]
func (h *HandlerService) PublicDeleteValidatorDashboard(w http.ResponseWriter, r *http.Request) {
var v validationError
dashboardId := v.checkPrimaryDashboardId(mux.Vars(r)["dashboard_id"])
@@ -259,6 +279,18 @@ func (h *HandlerService) PublicDeleteValidatorDashboard(w http.ResponseWriter, r
returnNoContent(w, r)
}
+// PublicPutValidatorDashboard godoc
+//
+// @Description Update the name of a specified validator dashboard.
+// @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.PublicPutValidatorDashboardName.request true "request"
+// @Success 200 {object} types.ApiDataResponse[types.VDBPostReturnData]
+// @Failure 400 {object} types.ApiErrorResponse
+// @Router /validator-dashboards/{dashboard_id}/name [put]
func (h *HandlerService) PublicPutValidatorDashboardName(w http.ResponseWriter, r *http.Request) {
var v validationError
dashboardId := v.checkPrimaryDashboardId(mux.Vars(r)["dashboard_id"])
@@ -286,6 +318,19 @@ func (h *HandlerService) PublicPutValidatorDashboardName(w http.ResponseWriter,
returnOk(w, r, response)
}
+// PublicPostValidatorDashboardGroups godoc
+//
+// @Description Create a new group in a specified validator dashboard.
+// @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.PublicPostValidatorDashboardGroups.request true "request"
+// @Success 201 {object} types.ApiDataResponse[types.VDBPostCreateGroupData]
+// @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 group limit."
+// @Router /validator-dashboards/{dashboard_id}/groups [post]
func (h *HandlerService) PublicPostValidatorDashboardGroups(w http.ResponseWriter, r *http.Request) {
var v validationError
dashboardId := v.checkPrimaryDashboardId(mux.Vars(r)["dashboard_id"])
@@ -337,6 +382,19 @@ func (h *HandlerService) PublicPostValidatorDashboardGroups(w http.ResponseWrite
returnCreated(w, r, response)
}
+// PublicGetValidatorDashboardGroups godoc
+//
+// @Description Update a groups name in a specified validator dashboard.
+// @Security ApiKeyInHeader || ApiKeyInQuery
+// @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 request body handlers.PublicPutValidatorDashboardGroups.request true "request"
+// @Success 200 {object} types.ApiDataResponse[types.VDBPostCreateGroupData]
+// @Failure 400 {object} types.ApiErrorResponse
+// @Router /validator-dashboards/{dashboard_id}/groups/{group_id} [put]
func (h *HandlerService) PublicPutValidatorDashboardGroups(w http.ResponseWriter, r *http.Request) {
var v validationError
vars := mux.Vars(r)
@@ -377,6 +435,18 @@ func (h *HandlerService) PublicPutValidatorDashboardGroups(w http.ResponseWriter
returnOk(w, r, response)
}
+// PublicDeleteValidatorDashboardGroups godoc
+//
+// @Description Delete a group in a specified validator dashboard.
+// @Tags Validator Dashboard Management
+// @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."
+// @Success 204 "Group deleted successfully."
+// @Failure 400 {object} types.ApiErrorResponse
+// @Router /validator-dashboards/{dashboard_id}/groups/{group_id} [delete]
func (h *HandlerService) PublicDeleteValidatorDashboardGroup(w http.ResponseWriter, r *http.Request) {
var v validationError
vars := mux.Vars(r)
@@ -408,6 +478,19 @@ func (h *HandlerService) PublicDeleteValidatorDashboardGroup(w http.ResponseWrit
returnNoContent(w, r)
}
+// PublicGetValidatorDashboardGroups godoc
+//
+// @Description Add new validators to a specified dashboard or update the group of already-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.
"
+// @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
dashboardId := v.checkPrimaryDashboardId(mux.Vars(r)["dashboard_id"])
@@ -531,6 +614,20 @@ func (h *HandlerService) PublicPostValidatorDashboardValidators(w http.ResponseW
returnCreated(w, r, response)
}
+// PublicGetValidatorDashboardValidators godoc
+//
+// @Description Get a list of groups in a specified validator dashboard.
+// @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 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(index, public_key, balance, status, withdrawal_credentials)
+// @Param search query string false "Search for Address, ENS."
+// @Success 200 {object} types.GetValidatorDashboardValidatorsResponse
+// @Failure 400 {object} types.ApiErrorResponse
+// @Router /validator-dashboards/{dashboard_id}/groups [get]
func (h *HandlerService) PublicGetValidatorDashboardValidators(w http.ResponseWriter, r *http.Request) {
var v validationError
dashboardId, err := h.handleDashboardId(r.Context(), mux.Vars(r)["dashboard_id"])
@@ -558,19 +655,30 @@ func (h *HandlerService) PublicGetValidatorDashboardValidators(w http.ResponseWr
returnOk(w, r, response)
}
+// PublicDeleteValidatorDashboardValidators godoc
+//
+// @Description Remove validators from a specified dashboard.
+// @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.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
+// @Router /validator-dashboards/{dashboard_id}/validators/bulk-deletions [post]
func (h *HandlerService) PublicDeleteValidatorDashboardValidators(w http.ResponseWriter, r *http.Request) {
var v validationError
dashboardId := v.checkPrimaryDashboardId(mux.Vars(r)["dashboard_id"])
- var indices []uint64
- var publicKeys []string
- req := struct {
+ type request struct {
Validators []intOrString `json:"validators"`
- }{}
+ }
+ var req request
if err := v.checkBody(&req, r); err != nil {
handleErr(w, r, err)
return
}
- indices, publicKeys = v.checkValidators(req.Validators, false)
+ indices, publicKeys := v.checkValidators(req.Validators, false)
if v.hasErrors() {
handleErr(w, r, v)
return
@@ -589,6 +697,19 @@ func (h *HandlerService) PublicDeleteValidatorDashboardValidators(w http.Respons
returnNoContent(w, r)
}
+// PublicPostValidatorDashboardPublicIds godoc
+//
+// @Description Create a new public ID for a specified dashboard. This can be used as an ID by other users for non-modyfing (i.e. GET) endpoints only. Currently limited to one per dashboard.
+// @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.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
+// @Failure 409 {object} types.ApiErrorResponse "Conflict. The request could not be performed by the server because the authenticated user has already reached their public ID limit."
+// @Router /validator-dashboards/{dashboard_id}/public-ids [post]
func (h *HandlerService) PublicPostValidatorDashboardPublicIds(w http.ResponseWriter, r *http.Request) {
var v validationError
dashboardId := v.checkPrimaryDashboardId(mux.Vars(r)["dashboard_id"])
@@ -623,13 +744,26 @@ func (h *HandlerService) PublicPostValidatorDashboardPublicIds(w http.ResponseWr
handleErr(w, r, err)
return
}
- response := types.ApiResponse{
- Data: data,
+ response := types.ApiDataResponse[types.VDBPublicId]{
+ Data: *data,
}
returnCreated(w, r, response)
}
+// PublicPutValidatorDashboardPublicId godoc
+//
+// @Description Update a specified public ID for a specified dashboard.
+// @Security ApiKeyInHeader || ApiKeyInQuery
+// @Tags Validator Dashboard Management
+// @Accept json
+// @Produce json
+// @Param dashboard_id path string 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]
+// @Failure 400 {object} types.ApiErrorResponse
+// @Router /validator-dashboards/{dashboard_id}/public-ids/{public_id} [put]
func (h *HandlerService) PublicPutValidatorDashboardPublicId(w http.ResponseWriter, r *http.Request) {
var v validationError
vars := mux.Vars(r)
@@ -666,13 +800,24 @@ func (h *HandlerService) PublicPutValidatorDashboardPublicId(w http.ResponseWrit
handleErr(w, r, err)
return
}
- response := types.ApiResponse{
- Data: data,
+ response := types.ApiDataResponse[types.VDBPublicId]{
+ Data: *data,
}
returnOk(w, r, response)
}
+// PublicDeleteValidatorDashboardPublicId godoc
+//
+// @Description Delete a specified public ID for a specified dashboard.
+// @Security ApiKeyInHeader || ApiKeyInQuery
+// @Tags Validator Dashboard Management
+// @Produce json
+// @Param dashboard_id path string 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
+// @Router /validator-dashboards/{dashboard_id}/public-ids/{public_id} [delete]
func (h *HandlerService) PublicDeleteValidatorDashboardPublicId(w http.ResponseWriter, r *http.Request) {
var v validationError
vars := mux.Vars(r)
@@ -701,12 +846,26 @@ func (h *HandlerService) PublicDeleteValidatorDashboardPublicId(w http.ResponseW
returnNoContent(w, r)
}
+// PublicPutValidatorDashboardArchiving godoc
+//
+// @Description Archive or unarchive a specified validator dashboard. Archived dashboards cannot be accessed by other endpoints. Archiving happens automatically if the number of dashboards, validators, or groups exceeds the limit allowed by your subscription plan. For example, this might occur if you downgrade your subscription to a lower tier.
+// @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.PublicPutValidatorDashboardArchiving.request true "request"
+// @Success 200 {object} types.ApiDataResponse[types.VDBPostArchivingReturnData]
+// @Failure 400 {object} types.ApiErrorResponse
+// @Conflict 409 {object} types.ApiErrorResponse "Conflict. The request could not be performed by the server because the authenticated user has already reached their subscription limit."
+// @Router /validator-dashboards/{dashboard_id}/archiving [put]
func (h *HandlerService) PublicPutValidatorDashboardArchiving(w http.ResponseWriter, r *http.Request) {
var v validationError
dashboardId := v.checkPrimaryDashboardId(mux.Vars(r)["dashboard_id"])
- req := struct {
+ type request struct {
IsArchived bool `json:"is_archived"`
- }{}
+ }
+ var req request
if err := v.checkBody(&req, r); err != nil {
handleErr(w, r, err)
return
@@ -784,6 +943,16 @@ func (h *HandlerService) PublicPutValidatorDashboardArchiving(w http.ResponseWri
returnOk(w, r, response)
}
+// PublicGetValidatorDashboardSlotViz godoc
+//
+// @Description Get slot viz information for a specified dashboard
+// @Tags Validator Dashboard
+// @Produce json
+// @Param dashboard_id path string true "The ID of the dashboard."
+// @Param group_ids query string false "Provide a comma separated list of group IDs to filter the results by. If omitted, all groups will be included."
+// @Success 200 {object} types.GetValidatorDashboardSlotVizResponse
+// @Failure 400 {object} types.ApiErrorResponse
+// @Router /validator-dashboards/{dashboard_id}/slot-viz [get]
func (h *HandlerService) PublicGetValidatorDashboardSlotViz(w http.ResponseWriter, r *http.Request) {
var v validationError
dashboardId, err := h.handleDashboardId(r.Context(), mux.Vars(r)["dashboard_id"])
@@ -809,6 +978,23 @@ func (h *HandlerService) PublicGetValidatorDashboardSlotViz(w http.ResponseWrite
returnOk(w, r, response)
}
+var summaryAllowedPeriods = []enums.TimePeriod{enums.TimePeriods.AllTime, enums.TimePeriods.Last30d, enums.TimePeriods.Last7d, enums.TimePeriods.Last24h, enums.TimePeriods.Last1h}
+
+// PublicGetValidatorDashboardSummary godoc
+//
+// @Description Get summary information for a specified dashboard
+// @Tags Validator Dashboard
+// @Produce json
+// @Param dashboard_id path string true "The ID of the dashboard."
+// @Param period query string true "Time period to get data for." Enums(all_time, last_30d, last_7d, last_24h, last_1h)
+// @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(group_id, validators, efficiency, attestations, proposals, reward)
+// @Param search query string false "Search for Index, Public Key, Group."
+// @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.GetValidatorDashboardSummaryResponse
+// @Failure 400 {object} types.ApiErrorResponse
+// @Router /validator-dashboards/{dashboard_id}/summary [get]
func (h *HandlerService) PublicGetValidatorDashboardSummary(w http.ResponseWriter, r *http.Request) {
var v validationError
dashboardId, err := h.handleDashboardId(r.Context(), mux.Vars(r)["dashboard_id"])
@@ -823,8 +1009,7 @@ func (h *HandlerService) PublicGetValidatorDashboardSummary(w http.ResponseWrite
period := checkEnum[enums.TimePeriod](&v, q.Get("period"), "period")
// allowed periods are: all_time, last_30d, last_7d, last_24h, last_1h
- allowedPeriods := []enums.TimePeriod{enums.TimePeriods.AllTime, enums.TimePeriods.Last30d, enums.TimePeriods.Last7d, enums.TimePeriods.Last24h, enums.TimePeriods.Last1h}
- checkEnumIsAllowed(&v, period, allowedPeriods, "period")
+ checkEnumIsAllowed(&v, period, summaryAllowedPeriods, "period")
if v.hasErrors() {
handleErr(w, r, v)
return
@@ -842,6 +1027,18 @@ func (h *HandlerService) PublicGetValidatorDashboardSummary(w http.ResponseWrite
returnOk(w, r, response)
}
+// PublicGetValidatorDashboardGroupSummary godoc
+//
+// @Description Get summary information for a specified group in a specified dashboard
+// @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 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
+// @Failure 400 {object} types.ApiErrorResponse
+// @Router /validator-dashboards/{dashboard_id}/groups/{group_id}/summary [get]
func (h *HandlerService) PublicGetValidatorDashboardGroupSummary(w http.ResponseWriter, r *http.Request) {
var v validationError
vars := mux.Vars(r)
@@ -859,8 +1056,7 @@ func (h *HandlerService) PublicGetValidatorDashboardGroupSummary(w http.Response
groupId := v.checkGroupId(vars["group_id"], forbidEmpty)
period := checkEnum[enums.TimePeriod](&v, r.URL.Query().Get("period"), "period")
// allowed periods are: all_time, last_30d, last_7d, last_24h, last_1h
- allowedPeriods := []enums.TimePeriod{enums.TimePeriods.AllTime, enums.TimePeriods.Last30d, enums.TimePeriods.Last7d, enums.TimePeriods.Last24h, enums.TimePeriods.Last1h}
- checkEnumIsAllowed(&v, period, allowedPeriods, "period")
+ checkEnumIsAllowed(&v, period, summaryAllowedPeriods, "period")
if v.hasErrors() {
handleErr(w, r, v)
return
@@ -877,6 +1073,20 @@ func (h *HandlerService) PublicGetValidatorDashboardGroupSummary(w http.Response
returnOk(w, r, response)
}
+// PublicGetValidatorDashboardSummaryChart godoc
+//
+// @Description Get summary chart data for a specified dashboard
+// @Tags Validator Dashboard
+// @Produce json
+// @Param dashboard_id path string true "The ID of the dashboard."
+// @Param group_ids query string false "Provide a comma separated list of group IDs to filter the results by."
+// @Param efficiency_type query string false "Efficiency type to get data for." Enums(all, attestation, sync, proposal)
+// @Param aggregation query string false "Aggregation type to get data for." Enums(epoch, hourly, daily, weekly) Default(hourly)
+// @Param after_ts query string false "Return data after this timestamp."
+// @Param before_ts query string false "Return data before this timestamp."
+// @Success 200 {object} types.GetValidatorDashboardSummaryChartResponse
+// @Failure 400 {object} types.ApiErrorResponse
+// @Router /validator-dashboards/{dashboard_id}/summary-chart [get]
func (h *HandlerService) PublicGetValidatorDashboardSummaryChart(w http.ResponseWriter, r *http.Request) {
var v validationError
ctx := r.Context()
@@ -916,6 +1126,18 @@ func (h *HandlerService) PublicGetValidatorDashboardSummaryChart(w http.Response
returnOk(w, r, response)
}
+// PublicGetValidatorDashboardSummaryValidators godoc
+//
+// @Description Get summary information for validators in a specified dashboard
+// @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 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
+// @Failure 400 {object} types.ApiErrorResponse
+// @Router /validator-dashboards/{dashboard_id}/summary/validators [get]
func (h *HandlerService) PublicGetValidatorDashboardSummaryValidators(w http.ResponseWriter, r *http.Request) {
var v validationError
dashboardId, err := h.handleDashboardId(r.Context(), mux.Vars(r)["dashboard_id"])
@@ -966,6 +1188,20 @@ func (h *HandlerService) PublicGetValidatorDashboardSummaryValidators(w http.Res
returnOk(w, r, response)
}
+// PublicGetValidatorDashboardRewards godoc
+//
+// @Description Get rewards information for a specified dashboard
+// @Tags Validator Dashboard
+// @Produce json
+// @Param dashboard_id path string true "The ID of the dashboard."
+// @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(epoch)
+// @Param search query string false "Search for Epoch, Index, Public Key, Group."
+// @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.GetValidatorDashboardRewardsResponse
+// @Failure 400 {object} types.ApiErrorResponse
+// @Router /validator-dashboards/{dashboard_id}/rewards [get]
func (h *HandlerService) PublicGetValidatorDashboardRewards(w http.ResponseWriter, r *http.Request) {
var v validationError
dashboardId, err := h.handleDashboardId(r.Context(), mux.Vars(r)["dashboard_id"])
@@ -994,6 +1230,18 @@ func (h *HandlerService) PublicGetValidatorDashboardRewards(w http.ResponseWrite
returnOk(w, r, response)
}
+// PublicGetValidatorDashboardGroupRewards godoc
+//
+// @Description Get rewards information for a specified group in a specified dashboard
+// @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 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
+// @Router /validator-dashboards/{dashboard_id}/groups/{group_id}/rewards/{epoch} [get]
func (h *HandlerService) PublicGetValidatorDashboardGroupRewards(w http.ResponseWriter, r *http.Request) {
var v validationError
vars := mux.Vars(r)
@@ -1022,6 +1270,16 @@ func (h *HandlerService) PublicGetValidatorDashboardGroupRewards(w http.Response
returnOk(w, r, response)
}
+// PublicGetValidatorDashboardRewardsChart godoc
+//
+// @Description Get rewards chart data for a specified dashboard
+// @Tags Validator Dashboard
+// @Produce json
+// @Param dashboard_id path string true "The ID of the dashboard."
+// @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.GetValidatorDashboardRewardsChartResponse
+// @Failure 400 {object} types.ApiErrorResponse
+// @Router /validator-dashboards/{dashboard_id}/rewards-chart [get]
func (h *HandlerService) PublicGetValidatorDashboardRewardsChart(w http.ResponseWriter, r *http.Request) {
var v validationError
vars := mux.Vars(r)
@@ -1048,6 +1306,22 @@ func (h *HandlerService) PublicGetValidatorDashboardRewardsChart(w http.Response
returnOk(w, r, response)
}
+// PublicGetValidatorDashboardDuties godoc
+//
+// @Description Get duties information for a specified dashboard
+// @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 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)
+// @Param search query string false "Search for Index, Public Key."
+// @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.GetValidatorDashboardDutiesResponse
+// @Failure 400 {object} types.ApiErrorResponse
+// @Router /validator-dashboards/{dashboard_id}/duties/{epoch} [get]
func (h *HandlerService) PublicGetValidatorDashboardDuties(w http.ResponseWriter, r *http.Request) {
var v validationError
vars := mux.Vars(r)
@@ -1079,6 +1353,20 @@ func (h *HandlerService) PublicGetValidatorDashboardDuties(w http.ResponseWriter
returnOk(w, r, response)
}
+// PublicGetValidatorDashboardBlocks godoc
+//
+// @Description Get blocks information for a specified dashboard
+// @Tags Validator Dashboard
+// @Produce json
+// @Param dashboard_id path string true "The ID of the dashboard."
+// @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(proposer, slot, block, status, reward)
+// @Param search query string false "Search for Index, Public Key, Group."
+// @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.GetValidatorDashboardBlocksResponse
+// @Failure 400 {object} types.ApiErrorResponse
+// @Router /validator-dashboards/{dashboard_id}/blocks [get]
func (h *HandlerService) PublicGetValidatorDashboardBlocks(w http.ResponseWriter, r *http.Request) {
var v validationError
dashboardId, err := h.handleDashboardId(r.Context(), mux.Vars(r)["dashboard_id"])
@@ -1107,6 +1395,19 @@ func (h *HandlerService) PublicGetValidatorDashboardBlocks(w http.ResponseWriter
returnOk(w, r, response)
}
+// PublicGetValidatorDashboardHeatmap godoc
+//
+// @Description Get heatmap information for a specified dashboard
+// @Tags Validator Dashboard
+// @Produce json
+// @Param dashboard_id path string true "The ID of the dashboard."
+// @Param aggregation query string false "Aggregation type to get data for." Enums(epoch, hourly, daily, weekly) Default(hourly)
+// @Param after_ts query string false "Return data after this timestamp."
+// @Param before_ts query string false "Return data before this timestamp."
+// @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.GetValidatorDashboardHeatmapResponse
+// @Failure 400 {object} types.ApiErrorResponse
+// @Router /validator-dashboards/{dashboard_id}/heatmap [get]
func (h *HandlerService) PublicGetValidatorDashboardHeatmap(w http.ResponseWriter, r *http.Request) {
var v validationError
dashboardId, err := h.handleDashboardId(r.Context(), mux.Vars(r)["dashboard_id"])
@@ -1143,6 +1444,19 @@ func (h *HandlerService) PublicGetValidatorDashboardHeatmap(w http.ResponseWrite
returnOk(w, r, response)
}
+// PublicGetValidatorDashboardGroupHeatmap godoc
+//
+// @Description Get heatmap information for a specified group in a specified dashboard
+// @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 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
+// @Failure 400 {object} types.ApiErrorResponse
+// @Router /validator-dashboards/{dashboard_id}/groups/{group_id}/heatmap/{timestamp} [get]
func (h *HandlerService) PublicGetValidatorDashboardGroupHeatmap(w http.ResponseWriter, r *http.Request) {
var v validationError
vars := mux.Vars(r)
@@ -1180,6 +1494,17 @@ func (h *HandlerService) PublicGetValidatorDashboardGroupHeatmap(w http.Response
returnOk(w, r, response)
}
+// PublicGetValidatorDashboardExecutionLayerDeposits godoc
+//
+// @Description Get execution layer deposits information for a specified dashboard
+// @Tags Validator Dashboard
+// @Produce json
+// @Param dashboard_id path string true "The ID of the dashboard."
+// @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."
+// @Success 200 {object} types.GetValidatorDashboardExecutionLayerDepositsResponse
+// @Failure 400 {object} types.ApiErrorResponse
+// @Router /validator-dashboards/{dashboard_id}/execution-layer-deposits [get]
func (h *HandlerService) PublicGetValidatorDashboardExecutionLayerDeposits(w http.ResponseWriter, r *http.Request) {
var v validationError
dashboardId, err := h.handleDashboardId(r.Context(), mux.Vars(r)["dashboard_id"])
@@ -1205,6 +1530,17 @@ func (h *HandlerService) PublicGetValidatorDashboardExecutionLayerDeposits(w htt
returnOk(w, r, response)
}
+// PublicGetValidatorDashboardConsensusLayerDeposits godoc
+//
+// @Description Get consensus layer deposits information for a specified dashboard
+// @Tags Validator Dashboard
+// @Produce json
+// @Param dashboard_id path string true "The ID of the dashboard."
+// @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."
+// @Success 200 {object} types.GetValidatorDashboardConsensusLayerDepositsResponse
+// @Failure 400 {object} types.ApiErrorResponse
+// @Router /validator-dashboards/{dashboard_id}/consensus-layer-deposits [get]
func (h *HandlerService) PublicGetValidatorDashboardConsensusLayerDeposits(w http.ResponseWriter, r *http.Request) {
var v validationError
dashboardId, err := h.handleDashboardId(r.Context(), mux.Vars(r)["dashboard_id"])
@@ -1231,6 +1567,15 @@ func (h *HandlerService) PublicGetValidatorDashboardConsensusLayerDeposits(w htt
returnOk(w, r, response)
}
+// PublicGetValidatorDashboardTotalConsensusLayerDeposits godoc
+//
+// @Description Get total consensus layer deposits information for a specified dashboard
+// @Tags Validator Dashboard
+// @Produce json
+// @Param dashboard_id path string true "The ID of the dashboard."
+// @Success 200 {object} types.GetValidatorDashboardTotalConsensusDepositsResponse
+// @Failure 400 {object} types.ApiErrorResponse
+// @Router /validator-dashboards/{dashboard_id}/total-consensus-layer-deposits [get]
func (h *HandlerService) PublicGetValidatorDashboardTotalConsensusLayerDeposits(w http.ResponseWriter, r *http.Request) {
var err error
dashboardId, err := h.handleDashboardId(r.Context(), mux.Vars(r)["dashboard_id"])
@@ -1250,6 +1595,15 @@ func (h *HandlerService) PublicGetValidatorDashboardTotalConsensusLayerDeposits(
returnOk(w, r, response)
}
+// PublicGetValidatorDashboardTotalExecutionLayerDeposits godoc
+//
+// @Description Get total execution layer deposits information for a specified dashboard
+// @Tags Validator Dashboard
+// @Produce json
+// @Param dashboard_id path string true "The ID of the dashboard."
+// @Success 200 {object} types.GetValidatorDashboardTotalExecutionDepositsResponse
+// @Failure 400 {object} types.ApiErrorResponse
+// @Router /validator-dashboards/{dashboard_id}/total-execution-layer-deposits [get]
func (h *HandlerService) PublicGetValidatorDashboardTotalExecutionLayerDeposits(w http.ResponseWriter, r *http.Request) {
var err error
dashboardId, err := h.handleDashboardId(r.Context(), mux.Vars(r)["dashboard_id"])
@@ -1269,6 +1623,20 @@ func (h *HandlerService) PublicGetValidatorDashboardTotalExecutionLayerDeposits(
returnOk(w, r, response)
}
+// PublicGetValidatorDashboardWithdrawals godoc
+//
+// @Description Get withdrawals information for a specified dashboard
+// @Tags Validator Dashboard
+// @Produce json
+// @Param dashboard_id path string true "The ID of the dashboard."
+// @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(epoch, slot, index, recipient, amount)
+// @Param search query string false "Search for Index, Public Key, Address."
+// @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.GetValidatorDashboardWithdrawalsResponse
+// @Failure 400 {object} types.ApiErrorResponse
+// @Router /validator-dashboards/{dashboard_id}/withdrawals [get]
func (h *HandlerService) PublicGetValidatorDashboardWithdrawals(w http.ResponseWriter, r *http.Request) {
var v validationError
q := r.URL.Query()
@@ -1297,6 +1665,16 @@ func (h *HandlerService) PublicGetValidatorDashboardWithdrawals(w http.ResponseW
returnOk(w, r, response)
}
+// PublicGetValidatorDashboardTotalWithdrawals godoc
+//
+// @Description Get total withdrawals information for a specified dashboard
+// @Tags Validator Dashboard
+// @Produce json
+// @Param dashboard_id path string true "The ID of the dashboard."
+// @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.GetValidatorDashboardTotalWithdrawalsResponse
+// @Failure 400 {object} types.ApiErrorResponse
+// @Router /validator-dashboards/{dashboard_id}/total-withdrawals [get]
func (h *HandlerService) PublicGetValidatorDashboardTotalWithdrawals(w http.ResponseWriter, r *http.Request) {
var v validationError
q := r.URL.Query()
@@ -1324,6 +1702,19 @@ func (h *HandlerService) PublicGetValidatorDashboardTotalWithdrawals(w http.Resp
returnOk(w, r, response)
}
+// PublicGetValidatorDashboardRocketPool godoc
+//
+// @Description Get an aggregated list of the Rocket Pool nodes details associated with a specified dashboard.
+// @Tags Validator Dashboard
+// @Produce json
+// @Param dashboard_id path string true "The ID of the dashboard."
+// @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(node, minipools, collateral, rpl, effective_rpl, rpl_apr, smoothing_pool)
+// @Param search query string false "Search for Node address."
+// @Success 200 {object} types.GetValidatorDashboardRocketPoolResponse
+// @Failure 400 {object} types.ApiErrorResponse
+// @Router /validator-dashboards/{dashboard_id}/rocket-pool [get]
func (h *HandlerService) PublicGetValidatorDashboardRocketPool(w http.ResponseWriter, r *http.Request) {
var v validationError
q := r.URL.Query()
@@ -1351,6 +1742,15 @@ func (h *HandlerService) PublicGetValidatorDashboardRocketPool(w http.ResponseWr
returnOk(w, r, response)
}
+// PublicGetValidatorDashboardTotalRocketPool godoc
+//
+// @Description Get a summary of all Rocket Pool nodes details associated with a specified dashboard.
+// @Tags Validator Dashboard
+// @Produce json
+// @Param dashboard_id path string true "The ID of the dashboard."
+// @Success 200 {object} types.GetValidatorDashboardTotalRocketPoolResponse
+// @Failure 400 {object} types.ApiErrorResponse
+// @Router /validator-dashboards/{dashboard_id}/total-rocket-pool [get]
func (h *HandlerService) PublicGetValidatorDashboardTotalRocketPool(w http.ResponseWriter, r *http.Request) {
var v validationError
q := r.URL.Query()
@@ -1376,6 +1776,16 @@ func (h *HandlerService) PublicGetValidatorDashboardTotalRocketPool(w http.Respo
returnOk(w, r, response)
}
+// PublicGetValidatorDashboardNodeRocketPool godoc
+//
+// @Description Get details for a specific Rocket Pool node associated with a specified dashboard.
+// @Tags Validator Dashboard
+// @Produce json
+// @Param dashboard_id path string true "The ID of the dashboard."
+// @Param node_address path string true "The address of the node."
+// @Success 200 {object} types.GetValidatorDashboardNodeRocketPoolResponse
+// @Failure 400 {object} types.ApiErrorResponse
+// @Router /validator-dashboards/{dashboard_id}/rocket-pool/{node_address} [get]
func (h *HandlerService) PublicGetValidatorDashboardNodeRocketPool(w http.ResponseWriter, r *http.Request) {
var v validationError
vars := mux.Vars(r)
@@ -1402,6 +1812,20 @@ func (h *HandlerService) PublicGetValidatorDashboardNodeRocketPool(w http.Respon
returnOk(w, r, response)
}
+// PublicGetValidatorDashboardRocketPoolMinipools godoc
+//
+// @Description Get minipools information for a specified Rocket Pool node associated with a specified dashboard.
+// @Tags Validator Dashboard
+// @Produce json
+// @Param dashboard_id path string true "The ID of the dashboard."
+// @Param node_address path string true "The address of the node."
+// @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. Possible values are TODO."
+// @Param search query string false "Search for Index, Node."
+// @Success 200 {object} types.GetValidatorDashboardRocketPoolMinipoolsResponse
+// @Failure 400 {object} types.ApiErrorResponse
+// @Router /validator-dashboards/{dashboard_id}/rocket-pool/{node_address}/minipools [get]
func (h *HandlerService) PublicGetValidatorDashboardRocketPoolMinipools(w http.ResponseWriter, r *http.Request) {
var v validationError
vars := mux.Vars(r)
diff --git a/backend/pkg/api/types/common.go b/backend/pkg/api/types/common.go
index e658afa80..2c7253623 100644
--- a/backend/pkg/api/types/common.go
+++ b/backend/pkg/api/types/common.go
@@ -40,8 +40,8 @@ type Address struct {
type LuckItem struct {
Percent float64 `json:"percent"`
- Expected time.Time `json:"expected"`
- Average time.Duration `json:"average"`
+ Expected time.Time `json:"expected" swaggertype:"string" format:"date-time"`
+ Average time.Duration `json:"average" swaggertype:"primitive,integer"`
}
type Luck struct {
diff --git a/backend/pkg/api/types/validator_dashboard.go b/backend/pkg/api/types/validator_dashboard.go
index 39d5e5d0d..380d035d4 100644
--- a/backend/pkg/api/types/validator_dashboard.go
+++ b/backend/pkg/api/types/validator_dashboard.go
@@ -27,7 +27,7 @@ type VDBOverviewBalances struct {
}
type VDBOverviewData struct {
- Name string `json:"name,omitempty"`
+ Name string `json:"name,omitempty" extensions:"x-order=1"`
Network uint64 `json:"network"`
Groups []VDBOverviewGroup `json:"groups"`
Validators VDBOverviewValidators `json:"validators"`
@@ -60,7 +60,7 @@ type VDBSummaryValidators struct {
}
type VDBSummaryTableRow struct {
- GroupId int64 `json:"group_id"`
+ GroupId int64 `json:"group_id" extensions:"x-order=1"`
Status VDBSummaryStatus `json:"status"`
Validators VDBSummaryValidators `json:"validators"`
Efficiency float64 `json:"efficiency"`
@@ -116,7 +116,7 @@ type GetValidatorDashboardSummaryChartResponse ApiDataResponse[ChartData[int, fl
// ------------------------------------------------------------
// Summary Validators
type VDBSummaryValidator struct {
- Index uint64 `json:"index"`
+ Index uint64 `json:"index" extensions:"x-order=1"`
DutyObjects []uint64 `json:"duty_objects,omitempty"`
}
type VDBSummaryValidatorsData struct {
@@ -169,7 +169,7 @@ type GetValidatorDashboardRewardsChartResponse ApiDataResponse[ChartData[int, de
// Duties Modal
type VDBEpochDutiesTableRow struct {
- Validator uint64 `json:"validator"`
+ Validator uint64 `json:"validator" extensions:"x-order=1"`
Duties ValidatorHistoryDuties `json:"duties"`
}
type GetValidatorDashboardDutiesResponse ApiPagingResponse[VDBEpochDutiesTableRow]
@@ -177,12 +177,12 @@ type GetValidatorDashboardDutiesResponse ApiPagingResponse[VDBEpochDutiesTableRo
// ------------------------------------------------------------
// Blocks Tab
type VDBBlocksTableRow struct {
- Proposer uint64 `json:"proposer"`
- GroupId uint64 `json:"group_id"`
- Epoch uint64 `json:"epoch"`
- Slot uint64 `json:"slot"`
+ Proposer uint64 `json:"proposer" extensions:"x-order=1"`
+ GroupId uint64 `json:"group_id" extensions:"x-order=2"`
+ Epoch uint64 `json:"epoch" extensions:"x-order=3"`
+ Slot uint64 `json:"slot" extensions:"x-order=4"`
+ Block *uint64 `json:"block,omitempty" extensions:"x-order=5"`
Status string `json:"status" tstype:"'success' | 'missed' | 'orphaned' | 'scheduled'" faker:"oneof: success, missed, orphaned, scheduled"`
- Block *uint64 `json:"block,omitempty"`
RewardRecipient *Address `json:"reward_recipient,omitempty"`
Reward *ClElValue[decimal.Decimal] `json:"reward,omitempty"`
Graffiti *string `json:"graffiti,omitempty"`
@@ -198,22 +198,22 @@ type VDBHeatmapEvents struct {
Sync bool `json:"sync"`
}
type VDBHeatmapCell struct {
- X int64 `json:"x"` // Timestamp
- Y uint64 `json:"y"` // Group ID
+ X int64 `json:"x" extensions:"x-order=1"` // Timestamp
+ Y uint64 `json:"y" extensions:"x-order=2"` // Group ID
- Value float64 `json:"value"` // Attestaton Rewards
+ Value float64 `json:"value" extensions:"x-order=3"` // Attestaton Rewards
Events *VDBHeatmapEvents `json:"events,omitempty"`
}
type VDBHeatmap struct {
- Timestamps []int64 `json:"timestamps"` // X-Axis Categories (unix timestamp)
- GroupIds []uint64 `json:"group_ids"` // Y-Axis Categories
- Data []VDBHeatmapCell `json:"data"`
+ Timestamps []int64 `json:"timestamps" extensions:"x-order=1"` // X-Axis Categories (unix timestamp)
+ GroupIds []uint64 `json:"group_ids" extensions:"x-order=2"` // Y-Axis Categories
+ Data []VDBHeatmapCell `json:"data" extensions:"x-order=3"`
Aggregation string `json:"aggregation" tstype:"'epoch' | 'hourly' | 'daily' | 'weekly'" faker:"oneof: epoch, hourly, daily, weekly"`
}
type GetValidatorDashboardHeatmapResponse ApiDataResponse[VDBHeatmap]
type VDBHeatmapTooltipData struct {
- Timestamp int64 `json:"timestamp"`
+ Timestamp int64 `json:"timestamp" extensions:"x-order=1"`
Proposers StatusCount `json:"proposers"`
Syncs uint64 `json:"syncs"`
@@ -290,7 +290,7 @@ type GetValidatorDashboardTotalWithdrawalsResponse ApiDataResponse[VDBTotalWithd
// ------------------------------------------------------------
// Rocket Pool Tab
type VDBRocketPoolTableRow struct {
- Node Address `json:"node"`
+ Node Address `json:"node" extensions:"x-order=1"`
Staked struct {
Eth decimal.Decimal `json:"eth"`
Rpl decimal.Decimal `json:"rpl"`
From 9d1196c558e4fc2ba69ff28500aadda108be1d6f Mon Sep 17 00:00:00 2001
From: Lucca <109136188+LuccaBitfly@users.noreply.github.com>
Date: Wed, 18 Sep 2024 09:29:09 +0200
Subject: [PATCH 132/187] (BEDS-464) annotate notification endpoint doc (#864)
---
backend/pkg/api/data_access/dummy.go | 4 +-
backend/pkg/api/data_access/notifications.go | 12 +-
.../api/enums/validator_dashboard_enums.go | 2 +-
backend/pkg/api/handlers/internal.go | 419 +----------
backend/pkg/api/handlers/public.go | 701 +++++++++++++++++-
backend/pkg/api/router.go | 39 +-
6 files changed, 743 insertions(+), 434 deletions(-)
diff --git a/backend/pkg/api/data_access/dummy.go b/backend/pkg/api/data_access/dummy.go
index 4590a106b..1dc80125d 100644
--- a/backend/pkg/api/data_access/dummy.go
+++ b/backend/pkg/api/data_access/dummy.go
@@ -452,11 +452,11 @@ func (d *DummyService) GetDashboardNotifications(ctx context.Context, userId uin
return getDummyWithPaging[t.NotificationDashboardsTableRow]()
}
-func (d *DummyService) GetValidatorDashboardNotificationDetails(ctx context.Context, notificationId string) (*t.NotificationValidatorDashboardDetail, error) {
+func (d *DummyService) GetValidatorDashboardNotificationDetails(ctx context.Context, dashboardId t.VDBIdPrimary, groupId uint64, epoch uint64) (*t.NotificationValidatorDashboardDetail, error) {
return getDummyStruct[t.NotificationValidatorDashboardDetail]()
}
-func (d *DummyService) GetAccountDashboardNotificationDetails(ctx context.Context, notificationId string) (*t.NotificationAccountDashboardDetail, error) {
+func (d *DummyService) GetAccountDashboardNotificationDetails(ctx context.Context, dashboardId uint64, groupId uint64, epoch uint64) (*t.NotificationAccountDashboardDetail, error) {
return getDummyStruct[t.NotificationAccountDashboardDetail]()
}
diff --git a/backend/pkg/api/data_access/notifications.go b/backend/pkg/api/data_access/notifications.go
index 4859b770c..94530d441 100644
--- a/backend/pkg/api/data_access/notifications.go
+++ b/backend/pkg/api/data_access/notifications.go
@@ -12,8 +12,8 @@ type NotificationsRepository interface {
GetDashboardNotifications(ctx context.Context, userId uint64, chainId uint64, cursor string, colSort t.Sort[enums.NotificationDashboardsColumn], search string, limit uint64) ([]t.NotificationDashboardsTableRow, *t.Paging, error)
// depending on how notifications are implemented, we may need to use something other than `notificationId` for identifying the notification
- GetValidatorDashboardNotificationDetails(ctx context.Context, notificationId string) (*t.NotificationValidatorDashboardDetail, error)
- GetAccountDashboardNotificationDetails(ctx context.Context, notificationId string) (*t.NotificationAccountDashboardDetail, error)
+ GetValidatorDashboardNotificationDetails(ctx context.Context, dashboardId t.VDBIdPrimary, groupId uint64, epoch uint64) (*t.NotificationValidatorDashboardDetail, error)
+ GetAccountDashboardNotificationDetails(ctx context.Context, dashboardId uint64, groupId uint64, epoch uint64) (*t.NotificationAccountDashboardDetail, error)
GetMachineNotifications(ctx context.Context, userId uint64, cursor string, colSort t.Sort[enums.NotificationMachinesColumn], search string, limit uint64) ([]t.NotificationMachinesTableRow, *t.Paging, error)
GetClientNotifications(ctx context.Context, userId uint64, cursor string, colSort t.Sort[enums.NotificationClientsColumn], search string, limit uint64) ([]t.NotificationClientsTableRow, *t.Paging, error)
@@ -37,12 +37,12 @@ func (d *DataAccessService) GetDashboardNotifications(ctx context.Context, userI
return d.dummy.GetDashboardNotifications(ctx, userId, chainId, cursor, colSort, search, limit)
}
-func (d *DataAccessService) GetValidatorDashboardNotificationDetails(ctx context.Context, notificationId string) (*t.NotificationValidatorDashboardDetail, error) {
- return d.dummy.GetValidatorDashboardNotificationDetails(ctx, notificationId)
+func (d *DataAccessService) GetValidatorDashboardNotificationDetails(ctx context.Context, dashboardId t.VDBIdPrimary, groupId uint64, epoch uint64) (*t.NotificationValidatorDashboardDetail, error) {
+ return d.dummy.GetValidatorDashboardNotificationDetails(ctx, dashboardId, groupId, epoch)
}
-func (d *DataAccessService) GetAccountDashboardNotificationDetails(ctx context.Context, notificationId string) (*t.NotificationAccountDashboardDetail, error) {
- return d.dummy.GetAccountDashboardNotificationDetails(ctx, notificationId)
+func (d *DataAccessService) GetAccountDashboardNotificationDetails(ctx context.Context, dashboardId uint64, groupId uint64, epoch uint64) (*t.NotificationAccountDashboardDetail, error) {
+ return d.dummy.GetAccountDashboardNotificationDetails(ctx, dashboardId, groupId, epoch)
}
func (d *DataAccessService) GetMachineNotifications(ctx context.Context, userId uint64, cursor string, colSort t.Sort[enums.NotificationMachinesColumn], search string, limit uint64) ([]t.NotificationMachinesTableRow, *t.Paging, error) {
diff --git a/backend/pkg/api/enums/validator_dashboard_enums.go b/backend/pkg/api/enums/validator_dashboard_enums.go
index 928d5742c..fcbfec71b 100644
--- a/backend/pkg/api/enums/validator_dashboard_enums.go
+++ b/backend/pkg/api/enums/validator_dashboard_enums.go
@@ -437,7 +437,7 @@ func (c VDBRocketPoolMinipoolsColumn) Int() int {
func (VDBRocketPoolMinipoolsColumn) NewFromString(s string) VDBRocketPoolMinipoolsColumn {
switch s {
- case "group":
+ case "group_id":
return VDBRocketPoolMinipoolsGroup
default:
return VDBRocketPoolMinipoolsColumn(-1)
diff --git a/backend/pkg/api/handlers/internal.go b/backend/pkg/api/handlers/internal.go
index be1d96ecb..0a0f5c006 100644
--- a/backend/pkg/api/handlers/internal.go
+++ b/backend/pkg/api/handlers/internal.go
@@ -2,7 +2,6 @@ package handlers
import (
"errors"
- "math"
"net/http"
"github.com/gobitfly/beaconchain/pkg/api/enums"
@@ -526,459 +525,79 @@ func (h *HandlerService) InternalPostMobileBundleDeliveries(w http.ResponseWrite
// Notifications
func (h *HandlerService) InternalGetUserNotifications(w http.ResponseWriter, r *http.Request) {
- userId, err := GetUserIdByContext(r)
- if err != nil {
- handleErr(w, r, err)
- return
- }
- data, err := h.dai.GetNotificationOverview(r.Context(), userId)
- if err != nil {
- handleErr(w, r, err)
- return
- }
- response := types.InternalGetUserNotificationsResponse{
- Data: *data,
- }
- returnOk(w, r, response)
+ h.PublicGetUserNotifications(w, r)
}
func (h *HandlerService) InternalGetUserNotificationDashboards(w http.ResponseWriter, r *http.Request) {
- var v validationError
- userId, err := GetUserIdByContext(r)
- if err != nil {
- handleErr(w, r, err)
- return
- }
- q := r.URL.Query()
- pagingParams := v.checkPagingParams(q)
- sort := checkSort[enums.NotificationDashboardsColumn](&v, q.Get("sort"))
- chainId := v.checkNetworkParameter(q.Get("network"))
- if v.hasErrors() {
- handleErr(w, r, v)
- return
- }
- data, paging, err := h.dai.GetDashboardNotifications(r.Context(), userId, chainId, pagingParams.cursor, *sort, pagingParams.search, pagingParams.limit)
- if err != nil {
- handleErr(w, r, err)
- return
- }
- response := types.InternalGetUserNotificationDashboardsResponse{
- Data: data,
- Paging: *paging,
- }
- returnOk(w, r, response)
+ h.PublicGetUserNotificationDashboards(w, r)
}
func (h *HandlerService) InternalGetUserNotificationsValidatorDashboard(w http.ResponseWriter, r *http.Request) {
- var v validationError
- notificationId := v.checkRegex(reNonEmpty, mux.Vars(r)["notification_id"], "notification_id")
- if v.hasErrors() {
- handleErr(w, r, v)
- return
- }
- data, err := h.dai.GetValidatorDashboardNotificationDetails(r.Context(), notificationId)
- if err != nil {
- handleErr(w, r, err)
- return
- }
- response := types.InternalGetUserNotificationsValidatorDashboardResponse{
- Data: *data,
- }
- returnOk(w, r, response)
+ h.PublicGetUserNotificationsValidatorDashboard(w, r)
}
func (h *HandlerService) InternalGetUserNotificationsAccountDashboard(w http.ResponseWriter, r *http.Request) {
- var v validationError
- notificationId := v.checkRegex(reNonEmpty, mux.Vars(r)["notification_id"], "notification_id")
- if v.hasErrors() {
- handleErr(w, r, v)
- return
- }
- data, err := h.dai.GetAccountDashboardNotificationDetails(r.Context(), notificationId)
- if err != nil {
- handleErr(w, r, err)
- return
- }
- response := types.InternalGetUserNotificationsAccountDashboardResponse{
- Data: *data,
- }
- returnOk(w, r, response)
+ h.PublicGetUserNotificationsAccountDashboard(w, r)
}
func (h *HandlerService) InternalGetUserNotificationMachines(w http.ResponseWriter, r *http.Request) {
- var v validationError
- userId, err := GetUserIdByContext(r)
- if err != nil {
- handleErr(w, r, err)
- return
- }
- q := r.URL.Query()
- pagingParams := v.checkPagingParams(q)
- sort := checkSort[enums.NotificationMachinesColumn](&v, q.Get("sort"))
- if v.hasErrors() {
- handleErr(w, r, v)
- return
- }
- data, paging, err := h.dai.GetMachineNotifications(r.Context(), userId, pagingParams.cursor, *sort, pagingParams.search, pagingParams.limit)
- if err != nil {
- handleErr(w, r, err)
- return
- }
- response := types.InternalGetUserNotificationMachinesResponse{
- Data: data,
- Paging: *paging,
- }
- returnOk(w, r, response)
+ h.PublicGetUserNotificationMachines(w, r)
}
func (h *HandlerService) InternalGetUserNotificationClients(w http.ResponseWriter, r *http.Request) {
- var v validationError
- userId, err := GetUserIdByContext(r)
- if err != nil {
- handleErr(w, r, err)
- return
- }
- q := r.URL.Query()
- pagingParams := v.checkPagingParams(q)
- sort := checkSort[enums.NotificationClientsColumn](&v, q.Get("sort"))
- if v.hasErrors() {
- handleErr(w, r, v)
- return
- }
- data, paging, err := h.dai.GetClientNotifications(r.Context(), userId, pagingParams.cursor, *sort, pagingParams.search, pagingParams.limit)
- if err != nil {
- handleErr(w, r, err)
- return
- }
- response := types.InternalGetUserNotificationClientsResponse{
- Data: data,
- Paging: *paging,
- }
- returnOk(w, r, response)
+ h.PublicGetUserNotificationClients(w, r)
}
func (h *HandlerService) InternalGetUserNotificationRocketPool(w http.ResponseWriter, r *http.Request) {
- var v validationError
- userId, err := GetUserIdByContext(r)
- if err != nil {
- handleErr(w, r, err)
- return
- }
- q := r.URL.Query()
- pagingParams := v.checkPagingParams(q)
- sort := checkSort[enums.NotificationRocketPoolColumn](&v, q.Get("sort"))
- if v.hasErrors() {
- handleErr(w, r, v)
- return
- }
- data, paging, err := h.dai.GetRocketPoolNotifications(r.Context(), userId, pagingParams.cursor, *sort, pagingParams.search, pagingParams.limit)
- if err != nil {
- handleErr(w, r, err)
- return
- }
- response := types.InternalGetUserNotificationRocketPoolResponse{
- Data: data,
- Paging: *paging,
- }
- returnOk(w, r, response)
+ h.PublicGetUserNotificationRocketPool(w, r)
}
func (h *HandlerService) InternalGetUserNotificationNetworks(w http.ResponseWriter, r *http.Request) {
- var v validationError
- userId, err := GetUserIdByContext(r)
- if err != nil {
- handleErr(w, r, err)
- return
- }
- q := r.URL.Query()
- pagingParams := v.checkPagingParams(q)
- sort := checkSort[enums.NotificationNetworksColumn](&v, q.Get("sort"))
- if v.hasErrors() {
- handleErr(w, r, v)
- return
- }
- data, paging, err := h.dai.GetNetworkNotifications(r.Context(), userId, pagingParams.cursor, *sort, pagingParams.search, pagingParams.limit)
- if err != nil {
- handleErr(w, r, err)
- return
- }
- response := types.InternalGetUserNotificationNetworksResponse{
- Data: data,
- Paging: *paging,
- }
- returnOk(w, r, response)
+ h.PublicGetUserNotificationNetworks(w, r)
}
func (h *HandlerService) InternalGetUserNotificationSettings(w http.ResponseWriter, r *http.Request) {
- userId, err := GetUserIdByContext(r)
- if err != nil {
- handleErr(w, r, err)
- return
- }
- data, err := h.dai.GetNotificationSettings(r.Context(), userId)
- if err != nil {
- handleErr(w, r, err)
- return
- }
- response := types.InternalGetUserNotificationSettingsResponse{
- Data: *data,
- }
- returnOk(w, r, response)
+ h.PublicGetUserNotificationSettings(w, r)
}
func (h *HandlerService) InternalPutUserNotificationSettingsGeneral(w http.ResponseWriter, r *http.Request) {
- var v validationError
- userId, err := GetUserIdByContext(r)
- if err != nil {
- handleErr(w, r, err)
- return
- }
- var req types.NotificationSettingsGeneral
- if err := v.checkBody(&req, r); err != nil {
- handleErr(w, r, err)
- return
- }
- checkMinMax(&v, req.MachineStorageUsageThreshold, 0, 1, "machine_storage_usage_threshold")
- checkMinMax(&v, req.MachineCpuUsageThreshold, 0, 1, "machine_cpu_usage_threshold")
- 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
- }
- err = h.dai.UpdateNotificationSettingsGeneral(r.Context(), userId, req)
- if err != nil {
- handleErr(w, r, err)
- return
- }
- response := types.InternalPutUserNotificationSettingsGeneralResponse{
- Data: req,
- }
- returnOk(w, r, response)
+ h.PublicPutUserNotificationSettingsGeneral(w, r)
}
func (h *HandlerService) InternalPutUserNotificationSettingsNetworks(w http.ResponseWriter, r *http.Request) {
- var v validationError
- userId, err := GetUserIdByContext(r)
- if err != nil {
- handleErr(w, r, err)
- return
- }
- var req types.NotificationSettingsNetwork
- if err := v.checkBody(&req, r); err != nil {
- handleErr(w, r, err)
- return
- }
- checkMinMax(&v, req.ParticipationRateThreshold, 0, 1, "participation_rate_threshold")
-
- chainId := v.checkNetworkParameter(mux.Vars(r)["network"])
- if v.hasErrors() {
- handleErr(w, r, v)
- return
- }
- err = h.dai.UpdateNotificationSettingsNetworks(r.Context(), userId, chainId, req)
- if err != nil {
- handleErr(w, r, err)
- return
- }
- response := types.InternalPutUserNotificationSettingsNetworksResponse{
- Data: types.NotificationNetwork{
- ChainId: chainId,
- Settings: req,
- },
- }
- returnOk(w, r, response)
+ h.PublicPutUserNotificationSettingsNetworks(w, r)
}
func (h *HandlerService) InternalPutUserNotificationSettingsPairedDevices(w http.ResponseWriter, r *http.Request) {
- var v validationError
- req := struct {
- Name string `json:"name,omitempty"`
- IsNotificationsEnabled bool `json:"is_notifications_enabled"`
- }{}
- if err := v.checkBody(&req, r); err != nil {
- handleErr(w, r, err)
- return
- }
- // TODO use a better way to validate the paired device id
- pairedDeviceId := v.checkRegex(reNonEmpty, mux.Vars(r)["paired_device_id"], "paired_device_id")
- name := v.checkNameNotEmpty(req.Name)
- if v.hasErrors() {
- handleErr(w, r, v)
- return
- }
- err := h.dai.UpdateNotificationSettingsPairedDevice(r.Context(), pairedDeviceId, name, req.IsNotificationsEnabled)
- if err != nil {
- handleErr(w, r, err)
- return
- }
- // TODO timestamp
- response := types.InternalPutUserNotificationSettingsPairedDevicesResponse{
- Data: types.NotificationPairedDevice{
- Id: pairedDeviceId,
- Name: req.Name,
- IsNotificationsEnabled: req.IsNotificationsEnabled,
- },
- }
-
- returnOk(w, r, response)
+ h.PublicPutUserNotificationSettingsPairedDevices(w, r)
}
func (h *HandlerService) InternalDeleteUserNotificationSettingsPairedDevices(w http.ResponseWriter, r *http.Request) {
- var v validationError
- // TODO use a better way to validate the paired device id
- pairedDeviceId := v.checkRegex(reNonEmpty, mux.Vars(r)["paired_device_id"], "paired_device_id")
- if v.hasErrors() {
- handleErr(w, r, v)
- return
- }
- err := h.dai.DeleteNotificationSettingsPairedDevice(r.Context(), pairedDeviceId)
- if err != nil {
- handleErr(w, r, err)
- return
- }
- returnNoContent(w, r)
+ h.PublicDeleteUserNotificationSettingsPairedDevices(w, r)
}
func (h *HandlerService) InternalGetUserNotificationSettingsDashboards(w http.ResponseWriter, r *http.Request) {
- var v validationError
- userId, err := GetUserIdByContext(r)
- if err != nil {
- handleErr(w, r, err)
- return
- }
- q := r.URL.Query()
- pagingParams := v.checkPagingParams(q)
- sort := checkSort[enums.NotificationSettingsDashboardColumn](&v, q.Get("sort"))
- if v.hasErrors() {
- handleErr(w, r, v)
- return
- }
- data, paging, err := h.dai.GetNotificationSettingsDashboards(r.Context(), userId, pagingParams.cursor, *sort, pagingParams.search, pagingParams.limit)
- if err != nil {
- handleErr(w, r, err)
- return
- }
- response := types.InternalGetUserNotificationSettingsDashboardsResponse{
- Data: data,
- Paging: *paging,
- }
- returnOk(w, r, response)
+ h.PublicGetUserNotificationSettingsDashboards(w, r)
}
func (h *HandlerService) InternalPutUserNotificationSettingsValidatorDashboard(w http.ResponseWriter, r *http.Request) {
- var v validationError
- var req types.NotificationSettingsValidatorDashboard
- if err := v.checkBody(&req, r); err != nil {
- handleErr(w, r, err)
- return
- }
- checkMinMax(&v, req.GroupOfflineThreshold, 0, 1, "group_offline_threshold")
- vars := mux.Vars(r)
- dashboardId := v.checkPrimaryDashboardId(vars["dashboard_id"])
- groupId := v.checkExistingGroupId(vars["group_id"])
- if v.hasErrors() {
- handleErr(w, r, v)
- return
- }
- err := h.dai.UpdateNotificationSettingsValidatorDashboard(r.Context(), dashboardId, groupId, req)
- if err != nil {
- handleErr(w, r, err)
- return
- }
- response := types.InternalPutUserNotificationSettingsValidatorDashboardResponse{
- Data: req,
- }
- returnOk(w, r, response)
+ h.PublicPutUserNotificationSettingsValidatorDashboard(w, r)
}
func (h *HandlerService) InternalPutUserNotificationSettingsAccountDashboard(w http.ResponseWriter, r *http.Request) {
- var v validationError
- req := struct {
- WebhookUrl string `json:"webhook_url"`
- IsWebhookDiscordEnabled bool `json:"is_webhook_discord_enabled"`
- IsIgnoreSpamTransactionsEnabled bool `json:"is_ignore_spam_transactions_enabled"`
- SubscribedChainIds []intOrString `json:"subscribed_chain_ids"`
-
- IsIncomingTransactionsSubscribed bool `json:"is_incoming_transactions_subscribed"`
- IsOutgoingTransactionsSubscribed bool `json:"is_outgoing_transactions_subscribed"`
- IsERC20TokenTransfersSubscribed bool `json:"is_erc20_token_transfers_subscribed"`
- ERC20TokenTransfersValueThreshold float64 `json:"erc20_token_transfers_value_threshold"` // 0 does not disable, is_erc20_token_transfers_subscribed determines if it's enabled
- IsERC721TokenTransfersSubscribed bool `json:"is_erc721_token_transfers_subscribed"`
- IsERC1155TokenTransfersSubscribed bool `json:"is_erc1155_token_transfers_subscribed"`
- }{}
- if err := v.checkBody(&req, r); err != nil {
- handleErr(w, r, err)
- return
- }
- chainIdMap := v.checkNetworkSlice(req.SubscribedChainIds)
- // convert to uint64[] slice
- chainIds := make([]uint64, len(chainIdMap))
- i := 0
- for k := range chainIdMap {
- chainIds[i] = k
- i++
- }
- checkMinMax(&v, req.ERC20TokenTransfersValueThreshold, 0, math.MaxFloat64, "group_offline_threshold")
- vars := mux.Vars(r)
- dashboardId := v.checkPrimaryDashboardId(vars["dashboard_id"])
- groupId := v.checkExistingGroupId(vars["group_id"])
- if v.hasErrors() {
- handleErr(w, r, v)
- return
- }
- settings := types.NotificationSettingsAccountDashboard{
- WebhookUrl: req.WebhookUrl,
- IsWebhookDiscordEnabled: req.IsWebhookDiscordEnabled,
- IsIgnoreSpamTransactionsEnabled: req.IsIgnoreSpamTransactionsEnabled,
- SubscribedChainIds: chainIds,
-
- IsIncomingTransactionsSubscribed: req.IsIncomingTransactionsSubscribed,
- IsOutgoingTransactionsSubscribed: req.IsOutgoingTransactionsSubscribed,
- IsERC20TokenTransfersSubscribed: req.IsERC20TokenTransfersSubscribed,
- ERC20TokenTransfersValueThreshold: req.ERC20TokenTransfersValueThreshold,
- IsERC721TokenTransfersSubscribed: req.IsERC721TokenTransfersSubscribed,
- IsERC1155TokenTransfersSubscribed: req.IsERC1155TokenTransfersSubscribed,
- }
- err := h.dai.UpdateNotificationSettingsAccountDashboard(r.Context(), dashboardId, groupId, settings)
- if err != nil {
- handleErr(w, r, err)
- return
- }
- response := types.InternalPutUserNotificationSettingsAccountDashboardResponse{
- Data: settings,
- }
- returnOk(w, r, response)
+ h.PublicPutUserNotificationSettingsAccountDashboard(w, r)
}
func (h *HandlerService) InternalPostUserNotificationsTestEmail(w http.ResponseWriter, r *http.Request) {
- // TODO
- returnOk(w, r, nil)
+ h.PublicPostUserNotificationsTestEmail(w, r)
}
func (h *HandlerService) InternalPostUserNotificationsTestPush(w http.ResponseWriter, r *http.Request) {
- // TODO
- returnOk(w, r, nil)
+ h.PublicPostUserNotificationsTestPush(w, r)
}
func (h *HandlerService) InternalPostUserNotificationsTestWebhook(w http.ResponseWriter, r *http.Request) {
- var v validationError
- req := struct {
- WebhookUrl string `json:"webhook_url"`
- IsDiscordWebhookEnabled bool `json:"is_discord_webhook_enabled,omitempty"`
- }{}
- if err := v.checkBody(&req, r); err != nil {
- handleErr(w, r, err)
- return
- }
- if v.hasErrors() {
- handleErr(w, r, v)
- return
- }
- // TODO
- returnOk(w, r, nil)
+ h.PublicPostUserNotificationsTestWebhook(w, r)
}
// --------------------------------------
diff --git a/backend/pkg/api/handlers/public.go b/backend/pkg/api/handlers/public.go
index 95df23ea7..a31ad6bb2 100644
--- a/backend/pkg/api/handlers/public.go
+++ b/backend/pkg/api/handlers/public.go
@@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
+ "math"
"net/http"
"reflect"
"time"
@@ -36,6 +37,8 @@ import (
// @in query
// @name api_key
+// @Validator Dashboard Management.n
+
func (h *HandlerService) PublicGetHealthz(w http.ResponseWriter, r *http.Request) {
var v validationError
showAll := v.checkBool(r.URL.Query().Get("show_all"), "show_all")
@@ -135,7 +138,7 @@ func (h *HandlerService) PublicPutAccountDashboardTransactionsSettings(w http.Re
//
// @Description Create a new validator dashboard. **Note**: New dashboards will automatically have a default group created.
// @Security ApiKeyInHeader || ApiKeyInQuery
-// @Tags Validator Dashboard
+// @Tags Validator Dashboard Management
// @Accept json
// @Produce json
// @Param request body handlers.PublicPostValidatorDashboards.request true "`name`: Specify the name of the dashboard. `network`: Specify the network for the dashboard. Possible options are:
`ethereum`
`gnosis`
"
@@ -621,7 +624,6 @@ func (h *HandlerService) PublicPostValidatorDashboardValidators(w http.ResponseW
// @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 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(index, public_key, balance, status, withdrawal_credentials)
// @Param search query string false "Search for Address, ENS."
@@ -662,8 +664,8 @@ 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 request body handlers.PublicDeleteValidatorDashboardValidators.request true "`validators`: Provide an array of validator indices or public keys that should get removed from the dashboard."
+// @Param dashboard_id path string 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
// @Router /validator-dashboards/{dashboard_id}/validators/bulk-deletions [post]
@@ -857,7 +859,7 @@ func (h *HandlerService) PublicDeleteValidatorDashboardPublicId(w http.ResponseW
// @Param request body handlers.PublicPutValidatorDashboardArchiving.request true "request"
// @Success 200 {object} types.ApiDataResponse[types.VDBPostArchivingReturnData]
// @Failure 400 {object} types.ApiErrorResponse
-// @Conflict 409 {object} types.ApiErrorResponse "Conflict. The request could not be performed by the server because the authenticated user has already reached their subscription limit."
+// @Conflict 409 {object} types.ApiErrorResponse "Conflict. The request could not be performed by the server because the authenticated user has already reached their subscription limit."
// @Router /validator-dashboards/{dashboard_id}/archiving [put]
func (h *HandlerService) PublicPutValidatorDashboardArchiving(w http.ResponseWriter, r *http.Request) {
var v validationError
@@ -1821,7 +1823,7 @@ func (h *HandlerService) PublicGetValidatorDashboardNodeRocketPool(w http.Respon
// @Param node_address path string true "The address of the node."
// @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. Possible values are TODO."
+// @Param sort query string false "The field you want to sort by. Append with `:desc` for descending order." Enums(group_id)
// @Param search query string false "Search for Index, Node."
// @Success 200 {object} types.GetValidatorDashboardRocketPoolMinipoolsResponse
// @Failure 400 {object} types.ApiErrorResponse
@@ -1856,6 +1858,693 @@ func (h *HandlerService) PublicGetValidatorDashboardRocketPoolMinipools(w http.R
returnOk(w, r, response)
}
+// ----------------------------------------------
+// Notifications
+// ----------------------------------------------
+
+// PublicGetUserNotifications godoc
+//
+// @Description Get an overview of your recent notifications.
+// @Security ApiKeyInHeader || ApiKeyInQuery
+// @Tags Notifications
+// @Produce json
+// @Success 200 {object} types.InternalGetUserNotificationsResponse
+// @Router /users/me/notifications [get]
+func (h *HandlerService) PublicGetUserNotifications(w http.ResponseWriter, r *http.Request) {
+ userId, err := GetUserIdByContext(r)
+ if err != nil {
+ handleErr(w, r, err)
+ return
+ }
+ data, err := h.dai.GetNotificationOverview(r.Context(), userId)
+ if err != nil {
+ handleErr(w, r, err)
+ return
+ }
+ response := types.InternalGetUserNotificationsResponse{
+ Data: *data,
+ }
+ returnOk(w, r, response)
+}
+
+// PublicGetUserNotificationDashboards godoc
+//
+// @Description Get a list of triggered notifications related to your dashboards.
+// @Security ApiKeyInHeader || ApiKeyInQuery
+// @Tags Notifications
+// @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 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
+// @Failure 400 {object} types.ApiErrorResponse
+// @Router /users/me/notifications/dashboards [get]
+func (h *HandlerService) PublicGetUserNotificationDashboards(w http.ResponseWriter, r *http.Request) {
+ var v validationError
+ userId, err := GetUserIdByContext(r)
+ if err != nil {
+ handleErr(w, r, err)
+ return
+ }
+ q := r.URL.Query()
+ pagingParams := v.checkPagingParams(q)
+ sort := checkSort[enums.NotificationDashboardsColumn](&v, q.Get("sort"))
+ chainId := v.checkNetworkParameter(q.Get("network"))
+ if v.hasErrors() {
+ handleErr(w, r, v)
+ return
+ }
+ data, paging, err := h.dai.GetDashboardNotifications(r.Context(), userId, chainId, pagingParams.cursor, *sort, pagingParams.search, pagingParams.limit)
+ if err != nil {
+ handleErr(w, r, err)
+ return
+ }
+ response := types.InternalGetUserNotificationDashboardsResponse{
+ Data: data,
+ Paging: *paging,
+ }
+ returnOk(w, r, response)
+}
+
+// PublicGetUserNotificationValidators godoc
+//
+// @Description Get a detailed view of a triggered notification related to a validator dashboard group at a specific epoch.
+// @Security ApiKeyInHeader || ApiKeyInQuery
+// @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."
+// @Success 200 {object} types.InternalGetUserNotificationsValidatorDashboardResponse
+// @Failure 400 {object} types.ApiErrorResponse
+// @Router /users/me/notifications/validator-dashboards/{dashboard_id}/groups/{group_id}/epochs/{epoch} [get]
+func (h *HandlerService) PublicGetUserNotificationsValidatorDashboard(w http.ResponseWriter, r *http.Request) {
+ var v validationError
+ vars := mux.Vars(r)
+ dashboardId := v.checkPrimaryDashboardId(vars["dashboard_id"])
+ groupId := v.checkExistingGroupId(vars["group_id"])
+ epoch := v.checkUint(vars["epoch"], "epoch")
+ if v.hasErrors() {
+ handleErr(w, r, v)
+ return
+ }
+ data, err := h.dai.GetValidatorDashboardNotificationDetails(r.Context(), dashboardId, groupId, epoch)
+ if err != nil {
+ handleErr(w, r, err)
+ return
+ }
+ response := types.InternalGetUserNotificationsValidatorDashboardResponse{
+ Data: *data,
+ }
+ returnOk(w, r, response)
+}
+
+// PublicGetUserNotificationsAccountDashboard godoc
+//
+// @Description Get a detailed view of a triggered notification related to an account dashboard group at a specific epoch.
+// @Security ApiKeyInHeader || ApiKeyInQuery
+// @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."
+// @Success 200 {object} types.InternalGetUserNotificationsAccountDashboardResponse
+// @Failure 400 {object} types.ApiErrorResponse
+// @Router /users/me/notifications/account-dashboards/{dashboard_id}/groups/{group_id}/epochs/{epoch} [get]
+func (h *HandlerService) PublicGetUserNotificationsAccountDashboard(w http.ResponseWriter, r *http.Request) {
+ var v validationError
+ vars := mux.Vars(r)
+ dashboardId := v.checkUint(vars["dashboard_id"], "dashboard_id")
+ groupId := v.checkExistingGroupId(vars["group_id"])
+ epoch := v.checkUint(vars["epoch"], "epoch")
+ if v.hasErrors() {
+ handleErr(w, r, v)
+ return
+ }
+ data, err := h.dai.GetAccountDashboardNotificationDetails(r.Context(), dashboardId, groupId, epoch)
+ if err != nil {
+ handleErr(w, r, err)
+ return
+ }
+ response := types.InternalGetUserNotificationsAccountDashboardResponse{
+ Data: *data,
+ }
+ returnOk(w, r, response)
+}
+
+// PublicGetUserNotificationMachines godoc
+//
+// @Description Get a list of triggered notifications related to your machines.
+// @Security ApiKeyInHeader || ApiKeyInQuery
+// @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 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
+// @Failure 400 {object} types.ApiErrorResponse
+// @Router /users/me/notifications/machines [get]
+func (h *HandlerService) PublicGetUserNotificationMachines(w http.ResponseWriter, r *http.Request) {
+ var v validationError
+ userId, err := GetUserIdByContext(r)
+ if err != nil {
+ handleErr(w, r, err)
+ return
+ }
+ q := r.URL.Query()
+ pagingParams := v.checkPagingParams(q)
+ sort := checkSort[enums.NotificationMachinesColumn](&v, q.Get("sort"))
+ if v.hasErrors() {
+ handleErr(w, r, v)
+ return
+ }
+ data, paging, err := h.dai.GetMachineNotifications(r.Context(), userId, pagingParams.cursor, *sort, pagingParams.search, pagingParams.limit)
+ if err != nil {
+ handleErr(w, r, err)
+ return
+ }
+ response := types.InternalGetUserNotificationMachinesResponse{
+ Data: data,
+ Paging: *paging,
+ }
+ returnOk(w, r, response)
+}
+
+// PublicGetUserNotificationClients godoc
+//
+// @Description Get a list of triggered notifications related to your clients.
+// @Security ApiKeyInHeader || ApiKeyInQuery
+// @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 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
+// @Failure 400 {object} types.ApiErrorResponse
+// @Router /users/me/notifications/clients [get]
+func (h *HandlerService) PublicGetUserNotificationClients(w http.ResponseWriter, r *http.Request) {
+ var v validationError
+ userId, err := GetUserIdByContext(r)
+ if err != nil {
+ handleErr(w, r, err)
+ return
+ }
+ q := r.URL.Query()
+ pagingParams := v.checkPagingParams(q)
+ sort := checkSort[enums.NotificationClientsColumn](&v, q.Get("sort"))
+ if v.hasErrors() {
+ handleErr(w, r, v)
+ return
+ }
+ data, paging, err := h.dai.GetClientNotifications(r.Context(), userId, pagingParams.cursor, *sort, pagingParams.search, pagingParams.limit)
+ if err != nil {
+ handleErr(w, r, err)
+ return
+ }
+ response := types.InternalGetUserNotificationClientsResponse{
+ Data: data,
+ Paging: *paging,
+ }
+ returnOk(w, r, response)
+}
+
+// PublicGetUserNotificationRocketPool godoc
+//
+// @Description Get a list of triggered notifications related to Rocket Pool.
+// @Security ApiKeyInHeader || ApiKeyInQuery
+// @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 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
+// @Failure 400 {object} types.ApiErrorResponse
+// @Router /users/me/notifications/rocket-pool [get]
+func (h *HandlerService) PublicGetUserNotificationRocketPool(w http.ResponseWriter, r *http.Request) {
+ var v validationError
+ userId, err := GetUserIdByContext(r)
+ if err != nil {
+ handleErr(w, r, err)
+ return
+ }
+ q := r.URL.Query()
+ pagingParams := v.checkPagingParams(q)
+ sort := checkSort[enums.NotificationRocketPoolColumn](&v, q.Get("sort"))
+ if v.hasErrors() {
+ handleErr(w, r, v)
+ return
+ }
+ data, paging, err := h.dai.GetRocketPoolNotifications(r.Context(), userId, pagingParams.cursor, *sort, pagingParams.search, pagingParams.limit)
+ if err != nil {
+ handleErr(w, r, err)
+ return
+ }
+ response := types.InternalGetUserNotificationRocketPoolResponse{
+ Data: data,
+ Paging: *paging,
+ }
+ returnOk(w, r, response)
+}
+
+// PublicGetUserNotificationNetworks godoc
+//
+// @Description Get a list of triggered notifications related to networks.
+// @Security ApiKeyInHeader || ApiKeyInQuery
+// @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 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
+// @Failure 400 {object} types.ApiErrorResponse
+// @Router /users/me/notifications/networks [get]
+func (h *HandlerService) PublicGetUserNotificationNetworks(w http.ResponseWriter, r *http.Request) {
+ var v validationError
+ userId, err := GetUserIdByContext(r)
+ if err != nil {
+ handleErr(w, r, err)
+ return
+ }
+ q := r.URL.Query()
+ pagingParams := v.checkPagingParams(q)
+ sort := checkSort[enums.NotificationNetworksColumn](&v, q.Get("sort"))
+ if v.hasErrors() {
+ handleErr(w, r, v)
+ return
+ }
+ data, paging, err := h.dai.GetNetworkNotifications(r.Context(), userId, pagingParams.cursor, *sort, pagingParams.search, pagingParams.limit)
+ if err != nil {
+ handleErr(w, r, err)
+ return
+ }
+ response := types.InternalGetUserNotificationNetworksResponse{
+ Data: data,
+ Paging: *paging,
+ }
+ returnOk(w, r, response)
+}
+
+// PublicGetUserNotificationPairedDevices godoc
+//
+// @Description Get notification settings for the authenticated user. Excludes dashboard notification settings.
+// @Security ApiKeyInHeader || ApiKeyInQuery
+// @Tags Notification Settings
+// @Produce json
+// @Success 200 {object} types.InternalGetUserNotificationSettingsResponse
+// @Router /users/me/notifications/settings [get]
+func (h *HandlerService) PublicGetUserNotificationSettings(w http.ResponseWriter, r *http.Request) {
+ userId, err := GetUserIdByContext(r)
+ if err != nil {
+ handleErr(w, r, err)
+ return
+ }
+ data, err := h.dai.GetNotificationSettings(r.Context(), userId)
+ if err != nil {
+ handleErr(w, r, err)
+ return
+ }
+ response := types.InternalGetUserNotificationSettingsResponse{
+ Data: *data,
+ }
+ returnOk(w, r, response)
+}
+
+// PublicPutUserNotificationSettingsGeneral godoc
+//
+// @Description Update general notification settings for the authenticated user.
+// @Security ApiKeyInHeader || ApiKeyInQuery
+// @Tags Notification Settings
+// @Accept json
+// @Produce json
+// @Param request body types.NotificationSettingsGeneral true "Notification settings"
+// @Success 200 {object} types.InternalPutUserNotificationSettingsGeneralResponse
+// @Failure 400 {object} types.ApiErrorResponse
+// @Router /users/me/notifications/settings/general [put]
+func (h *HandlerService) PublicPutUserNotificationSettingsGeneral(w http.ResponseWriter, r *http.Request) {
+ var v validationError
+ userId, err := GetUserIdByContext(r)
+ if err != nil {
+ handleErr(w, r, err)
+ return
+ }
+ var req types.NotificationSettingsGeneral
+ if err := v.checkBody(&req, r); err != nil {
+ handleErr(w, r, err)
+ return
+ }
+ checkMinMax(&v, req.MachineStorageUsageThreshold, 0, 1, "machine_storage_usage_threshold")
+ checkMinMax(&v, req.MachineCpuUsageThreshold, 0, 1, "machine_cpu_usage_threshold")
+ 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
+ }
+ err = h.dai.UpdateNotificationSettingsGeneral(r.Context(), userId, req)
+ if err != nil {
+ handleErr(w, r, err)
+ return
+ }
+ response := types.InternalPutUserNotificationSettingsGeneralResponse{
+ Data: req,
+ }
+ returnOk(w, r, response)
+}
+
+// PublicPutUserNotificationSettingsNetworks godoc
+//
+// @Description Update network notification settings for the authenticated user.
+// @Security ApiKeyInHeader || ApiKeyInQuery
+// @Tags Notification Settings
+// @Accept json
+// @Produce json
+// @Param network path string true "The networks name or chain ID."
+// @Param request body types.NotificationSettingsNetwork true "Notification settings"
+// @Success 200 {object} types.InternalPutUserNotificationSettingsNetworksResponse
+// @Failure 400 {object} types.ApiErrorResponse
+// @Router /users/me/notifications/settings/networks/{network} [put]
+func (h *HandlerService) PublicPutUserNotificationSettingsNetworks(w http.ResponseWriter, r *http.Request) {
+ var v validationError
+ userId, err := GetUserIdByContext(r)
+ if err != nil {
+ handleErr(w, r, err)
+ return
+ }
+ var req types.NotificationSettingsNetwork
+ if err := v.checkBody(&req, r); err != nil {
+ handleErr(w, r, err)
+ return
+ }
+ checkMinMax(&v, req.ParticipationRateThreshold, 0, 1, "participation_rate_threshold")
+
+ chainId := v.checkNetworkParameter(mux.Vars(r)["network"])
+ if v.hasErrors() {
+ handleErr(w, r, v)
+ return
+ }
+ err = h.dai.UpdateNotificationSettingsNetworks(r.Context(), userId, chainId, req)
+ if err != nil {
+ handleErr(w, r, err)
+ return
+ }
+ response := types.InternalPutUserNotificationSettingsNetworksResponse{
+ Data: types.NotificationNetwork{
+ ChainId: chainId,
+ Settings: req,
+ },
+ }
+ returnOk(w, r, response)
+}
+
+// PublicPutUserNotificationSettingsPairedDevices godoc
+//
+// @Description Update paired device notification settings for the authenticated user.
+// @Security ApiKeyInHeader || ApiKeyInQuery
+// @Tags Notification Settings
+// @Accept json
+// @Produce json
+// @Param paired_device_id path string true "The paired device ID."
+// @Param request body handlers.PublicPutUserNotificationSettingsPairedDevices.request true "Notification settings"
+// @Success 200 {object} types.InternalPutUserNotificationSettingsPairedDevicesResponse
+// @Failure 400 {object} types.ApiErrorResponse
+// @Router /users/me/notifications/settings/paired-devices/{paired_device_id} [put]
+func (h *HandlerService) PublicPutUserNotificationSettingsPairedDevices(w http.ResponseWriter, r *http.Request) {
+ var v validationError
+ type request struct {
+ Name string `json:"name,omitempty"`
+ IsNotificationsEnabled bool `json:"is_notifications_enabled"`
+ }
+ var req request
+ if err := v.checkBody(&req, r); err != nil {
+ handleErr(w, r, err)
+ return
+ }
+ // TODO use a better way to validate the paired device id
+ pairedDeviceId := v.checkRegex(reNonEmpty, mux.Vars(r)["paired_device_id"], "paired_device_id")
+ name := v.checkNameNotEmpty(req.Name)
+ if v.hasErrors() {
+ handleErr(w, r, v)
+ return
+ }
+ err := h.dai.UpdateNotificationSettingsPairedDevice(r.Context(), pairedDeviceId, name, req.IsNotificationsEnabled)
+ if err != nil {
+ handleErr(w, r, err)
+ return
+ }
+ // TODO timestamp
+ response := types.InternalPutUserNotificationSettingsPairedDevicesResponse{
+ Data: types.NotificationPairedDevice{
+ Id: pairedDeviceId,
+ Name: req.Name,
+ IsNotificationsEnabled: req.IsNotificationsEnabled,
+ },
+ }
+
+ returnOk(w, r, response)
+}
+
+// PublicDeleteUserNotificationSettingsPairedDevices godoc
+//
+// @Description Delete paired device notification settings for the authenticated user.
+// @Security ApiKeyInHeader || ApiKeyInQuery
+// @Tags Notification Settings
+// @Produce json
+// @Param paired_device_id path string true "The paired device ID."
+// @Success 204
+// @Failure 400 {object} types.ApiErrorResponse
+// @Router /users/me/notifications/settings/paired-devices/{paired_device_id} [delete]
+func (h *HandlerService) PublicDeleteUserNotificationSettingsPairedDevices(w http.ResponseWriter, r *http.Request) {
+ var v validationError
+ // TODO use a better way to validate the paired device id
+ pairedDeviceId := v.checkRegex(reNonEmpty, mux.Vars(r)["paired_device_id"], "paired_device_id")
+ if v.hasErrors() {
+ handleErr(w, r, v)
+ return
+ }
+ err := h.dai.DeleteNotificationSettingsPairedDevice(r.Context(), pairedDeviceId)
+ if err != nil {
+ handleErr(w, r, err)
+ return
+ }
+ returnNoContent(w, r)
+}
+
+// PublicGetUserNotificationSettingsDashboards godoc
+//
+// @Description Get a list of notification settings for the dashboards of the authenticated user.
+// @Security ApiKeyInHeader || ApiKeyInQuery
+// @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 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
+// @Failure 400 {object} types.ApiErrorResponse
+// @Router /users/me/notifications/settings/dashboards [get]
+func (h *HandlerService) PublicGetUserNotificationSettingsDashboards(w http.ResponseWriter, r *http.Request) {
+ var v validationError
+ userId, err := GetUserIdByContext(r)
+ if err != nil {
+ handleErr(w, r, err)
+ return
+ }
+ q := r.URL.Query()
+ pagingParams := v.checkPagingParams(q)
+ sort := checkSort[enums.NotificationSettingsDashboardColumn](&v, q.Get("sort"))
+ if v.hasErrors() {
+ handleErr(w, r, v)
+ return
+ }
+ data, paging, err := h.dai.GetNotificationSettingsDashboards(r.Context(), userId, pagingParams.cursor, *sort, pagingParams.search, pagingParams.limit)
+ if err != nil {
+ handleErr(w, r, err)
+ return
+ }
+ response := types.InternalGetUserNotificationSettingsDashboardsResponse{
+ Data: data,
+ Paging: *paging,
+ }
+ returnOk(w, r, response)
+}
+
+// PublicPutUserNotificationSettingsValidatorDashboard godoc
+//
+// @Description Update the notification settings for a specific group of a validator dashboard for the authenticated user.
+// @Security ApiKeyInHeader || ApiKeyInQuery
+// @Tags Notification Settings
+// @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 request body types.NotificationSettingsValidatorDashboard true "Notification settings"
+// @Success 200 {object} types.InternalPutUserNotificationSettingsValidatorDashboardResponse
+// @Failure 400 {object} types.ApiErrorResponse
+// @Router /users/me/notifications/settings/validator-dashboards/{dashboard_id}/groups/{group_id} [put]
+func (h *HandlerService) PublicPutUserNotificationSettingsValidatorDashboard(w http.ResponseWriter, r *http.Request) {
+ var v validationError
+ var req types.NotificationSettingsValidatorDashboard
+ if err := v.checkBody(&req, r); err != nil {
+ handleErr(w, r, err)
+ return
+ }
+ checkMinMax(&v, req.GroupOfflineThreshold, 0, 1, "group_offline_threshold")
+ vars := mux.Vars(r)
+ dashboardId := v.checkPrimaryDashboardId(vars["dashboard_id"])
+ groupId := v.checkExistingGroupId(vars["group_id"])
+ if v.hasErrors() {
+ handleErr(w, r, v)
+ return
+ }
+ err := h.dai.UpdateNotificationSettingsValidatorDashboard(r.Context(), dashboardId, groupId, req)
+ if err != nil {
+ handleErr(w, r, err)
+ return
+ }
+ response := types.InternalPutUserNotificationSettingsValidatorDashboardResponse{
+ Data: req,
+ }
+ returnOk(w, r, response)
+}
+
+// PublicPutUserNotificationSettingsAccountDashboard godoc
+//
+// @Description Update the notification settings for a specific group of an account dashboard for the authenticated user.
+// @Security ApiKeyInHeader || ApiKeyInQuery
+// @Tags Notification Settings
+// @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 request body handlers.PublicPutUserNotificationSettingsAccountDashboard.request true "Notification settings"
+// @Success 200 {object} types.InternalPutUserNotificationSettingsAccountDashboardResponse
+// @Failure 400 {object} types.ApiErrorResponse
+// @Router /users/me/notifications/settings/account-dashboards/{dashboard_id}/groups/{group_id} [put]
+func (h *HandlerService) PublicPutUserNotificationSettingsAccountDashboard(w http.ResponseWriter, r *http.Request) {
+ var v validationError
+ // uses a different struct due to `subscribed_chain_ids`, which is a slice of intOrString in the payload but a slice of uint64 in the response
+ type request struct {
+ WebhookUrl string `json:"webhook_url"`
+ IsWebhookDiscordEnabled bool `json:"is_webhook_discord_enabled"`
+ IsIgnoreSpamTransactionsEnabled bool `json:"is_ignore_spam_transactions_enabled"`
+ SubscribedChainIds []intOrString `json:"subscribed_chain_ids"`
+
+ IsIncomingTransactionsSubscribed bool `json:"is_incoming_transactions_subscribed"`
+ IsOutgoingTransactionsSubscribed bool `json:"is_outgoing_transactions_subscribed"`
+ IsERC20TokenTransfersSubscribed bool `json:"is_erc20_token_transfers_subscribed"`
+ ERC20TokenTransfersValueThreshold float64 `json:"erc20_token_transfers_value_threshold"` // 0 does not disable, is_erc20_token_transfers_subscribed determines if it's enabled
+ IsERC721TokenTransfersSubscribed bool `json:"is_erc721_token_transfers_subscribed"`
+ IsERC1155TokenTransfersSubscribed bool `json:"is_erc1155_token_transfers_subscribed"`
+ }
+ var req request
+ if err := v.checkBody(&req, r); err != nil {
+ handleErr(w, r, err)
+ return
+ }
+ chainIdMap := v.checkNetworkSlice(req.SubscribedChainIds)
+ // convert to uint64[] slice
+ chainIds := make([]uint64, len(chainIdMap))
+ i := 0
+ for k := range chainIdMap {
+ chainIds[i] = k
+ i++
+ }
+ checkMinMax(&v, req.ERC20TokenTransfersValueThreshold, 0, math.MaxFloat64, "group_offline_threshold")
+ vars := mux.Vars(r)
+ dashboardId := v.checkPrimaryDashboardId(vars["dashboard_id"])
+ groupId := v.checkExistingGroupId(vars["group_id"])
+ if v.hasErrors() {
+ handleErr(w, r, v)
+ return
+ }
+ settings := types.NotificationSettingsAccountDashboard{
+ WebhookUrl: req.WebhookUrl,
+ IsWebhookDiscordEnabled: req.IsWebhookDiscordEnabled,
+ IsIgnoreSpamTransactionsEnabled: req.IsIgnoreSpamTransactionsEnabled,
+ SubscribedChainIds: chainIds,
+
+ IsIncomingTransactionsSubscribed: req.IsIncomingTransactionsSubscribed,
+ IsOutgoingTransactionsSubscribed: req.IsOutgoingTransactionsSubscribed,
+ IsERC20TokenTransfersSubscribed: req.IsERC20TokenTransfersSubscribed,
+ ERC20TokenTransfersValueThreshold: req.ERC20TokenTransfersValueThreshold,
+ IsERC721TokenTransfersSubscribed: req.IsERC721TokenTransfersSubscribed,
+ IsERC1155TokenTransfersSubscribed: req.IsERC1155TokenTransfersSubscribed,
+ }
+ err := h.dai.UpdateNotificationSettingsAccountDashboard(r.Context(), dashboardId, groupId, settings)
+ if err != nil {
+ handleErr(w, r, err)
+ return
+ }
+ response := types.InternalPutUserNotificationSettingsAccountDashboardResponse{
+ Data: settings,
+ }
+ returnOk(w, r, response)
+}
+
+// PublicPostUserNotificationsTestEmail godoc
+//
+// @Description Send a test email notification to the authenticated user.
+// @Security ApiKeyInHeader || ApiKeyInQuery
+// @Tags Notification Settings
+// @Produce json
+// @Success 204
+// @Router /users/me/notifications/test-email [post]
+func (h *HandlerService) PublicPostUserNotificationsTestEmail(w http.ResponseWriter, r *http.Request) {
+ // TODO
+ returnNoContent(w, r)
+}
+
+// PublicPostUserNotificationsTestPush godoc
+//
+// @Description Send a test push notification to the authenticated user.
+// @Security ApiKeyInHeader || ApiKeyInQuery
+// @Tags Notification Settings
+// @Produce json
+// @Success 204
+// @Router /users/me/notifications/test-push [post]
+func (h *HandlerService) PublicPostUserNotificationsTestPush(w http.ResponseWriter, r *http.Request) {
+ // TODO
+ returnNoContent(w, r)
+}
+
+// PublicPostUserNotificationsTestWebhook godoc
+//
+// @Description Send a test webhook notification from the authenticated user to the given URL.
+// @Security ApiKeyInHeader || ApiKeyInQuery
+// @Tags Notification Settings
+// @Accept json
+// @Produce json
+// @Param request body handlers.PublicPostUserNotificationsTestWebhook.request true "Request"
+// @Success 204
+// @Failure 400 {object} types.ApiErrorResponse
+// @Router /users/me/notifications/test-webhook [post]
+func (h *HandlerService) PublicPostUserNotificationsTestWebhook(w http.ResponseWriter, r *http.Request) {
+ var v validationError
+ type request struct {
+ WebhookUrl string `json:"webhook_url"`
+ IsDiscordWebhookEnabled bool `json:"is_discord_webhook_enabled,omitempty"`
+ }
+ var req request
+ if err := v.checkBody(&req, r); err != nil {
+ handleErr(w, r, err)
+ return
+ }
+ if v.hasErrors() {
+ handleErr(w, r, v)
+ return
+ }
+ // TODO
+ returnNoContent(w, r)
+}
+
func (h *HandlerService) PublicGetNetworkValidators(w http.ResponseWriter, r *http.Request) {
returnOk(w, r, nil)
}
diff --git a/backend/pkg/api/router.go b/backend/pkg/api/router.go
index 5338b5ccd..3238257bd 100644
--- a/backend/pkg/api/router.go
+++ b/backend/pkg/api/router.go
@@ -317,35 +317,36 @@ func addNotificationRoutes(hs *handlers.HandlerService, publicRouter, internalRo
publicNotificationRouter.Use(hs.ManageViaApiCheckMiddleware)
}
endpoints := []endpoint{
- {http.MethodGet, "", nil, hs.InternalGetUserNotifications},
- {http.MethodGet, "/dashboards", nil, hs.InternalGetUserNotificationDashboards},
- {http.MethodGet, "/validator-dashboards/{notification_id}", nil, hs.InternalGetUserNotificationsValidatorDashboard},
- {http.MethodGet, "/account-dashboards/{notification_id}", nil, hs.InternalGetUserNotificationsAccountDashboard},
- {http.MethodGet, "/machines", nil, hs.InternalGetUserNotificationMachines},
- {http.MethodGet, "/clients", nil, hs.InternalGetUserNotificationClients},
- {http.MethodGet, "/rocket-pool", nil, hs.InternalGetUserNotificationRocketPool},
- {http.MethodGet, "/networks", nil, hs.InternalGetUserNotificationNetworks},
- {http.MethodGet, "/settings", nil, hs.InternalGetUserNotificationSettings},
- {http.MethodPut, "/settings/general", nil, hs.InternalPutUserNotificationSettingsGeneral},
- {http.MethodPut, "/settings/networks/{network}", nil, hs.InternalPutUserNotificationSettingsNetworks},
- {http.MethodPut, "/settings/paired-devices/{paired_device_id}", nil, hs.InternalPutUserNotificationSettingsPairedDevices},
- {http.MethodDelete, "/settings/paired-devices/{paired_device_id}", nil, hs.InternalDeleteUserNotificationSettingsPairedDevices},
- {http.MethodGet, "/settings/dashboards", nil, hs.InternalGetUserNotificationSettingsDashboards},
- {http.MethodPost, "/test-email", nil, hs.InternalPostUserNotificationsTestEmail},
- {http.MethodPost, "/test-push", nil, hs.InternalPostUserNotificationsTestPush},
- {http.MethodPost, "/test-webhook", nil, hs.InternalPostUserNotificationsTestWebhook},
+ {http.MethodGet, "", hs.PublicGetUserNotifications, hs.InternalGetUserNotifications},
+ {http.MethodGet, "/dashboards", hs.PublicGetUserNotificationDashboards, hs.InternalGetUserNotificationDashboards},
+ {http.MethodGet, "/machines", hs.PublicGetUserNotificationMachines, hs.InternalGetUserNotificationMachines},
+ {http.MethodGet, "/clients", hs.PublicGetUserNotificationClients, hs.InternalGetUserNotificationClients},
+ {http.MethodGet, "/rocket-pool", hs.PublicGetUserNotificationRocketPool, hs.InternalGetUserNotificationRocketPool},
+ {http.MethodGet, "/networks", hs.PublicGetUserNotificationNetworks, hs.InternalGetUserNotificationNetworks},
+ {http.MethodGet, "/settings", hs.PublicGetUserNotificationSettings, hs.InternalGetUserNotificationSettings},
+ {http.MethodPut, "/settings/general", hs.PublicPutUserNotificationSettingsGeneral, hs.InternalPutUserNotificationSettingsGeneral},
+ {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.MethodGet, "/settings/dashboards", hs.PublicGetUserNotificationSettingsDashboards, hs.InternalGetUserNotificationSettingsDashboards},
+ {http.MethodPost, "/test-email", hs.PublicPostUserNotificationsTestEmail, hs.InternalPostUserNotificationsTestEmail},
+ {http.MethodPost, "/test-push", hs.PublicPostUserNotificationsTestPush, hs.InternalPostUserNotificationsTestPush},
+ {http.MethodPost, "/test-webhook", hs.PublicPostUserNotificationsTestWebhook, hs.InternalPostUserNotificationsTestWebhook},
}
addEndpointsToRouters(endpoints, publicNotificationRouter, internalNotificationRouter)
publicDashboardNotificationSettingsRouter := publicNotificationRouter.NewRoute().Subrouter()
internalDashboardNotificationSettingsRouter := internalNotificationRouter.NewRoute().Subrouter()
+ // TODO add adb auth middleware to account dashboard endpoints once they are implemented
if !debug {
publicDashboardNotificationSettingsRouter.Use(hs.VDBAuthMiddleware)
internalDashboardNotificationSettingsRouter.Use(hs.VDBAuthMiddleware)
}
dashboardSettingsEndpoints := []endpoint{
- {http.MethodPut, "/settings/validator-dashboards/{dashboard_id}/groups/{group_id}", nil, hs.InternalPutUserNotificationSettingsValidatorDashboard},
- {http.MethodPut, "/settings/account-dashboards/{dashboard_id}/groups/{group_id}", nil, hs.InternalPutUserNotificationSettingsAccountDashboard},
+ {http.MethodGet, "/validator-dashboards/{dashboard_id}/groups/{group_id}/epochs/{epoch}", hs.PublicGetUserNotificationsValidatorDashboard, hs.InternalGetUserNotificationsValidatorDashboard},
+ {http.MethodGet, "/account-dashboards/{dashboard_id}/groups/{group_id}/epochs/{epoch}", hs.PublicGetUserNotificationsAccountDashboard, hs.InternalGetUserNotificationsAccountDashboard},
+ {http.MethodPut, "/settings/validator-dashboards/{dashboard_id}/groups/{group_id}", hs.PublicPutUserNotificationSettingsValidatorDashboard, hs.InternalPutUserNotificationSettingsValidatorDashboard},
+ {http.MethodPut, "/settings/account-dashboards/{dashboard_id}/groups/{group_id}", hs.PublicPutUserNotificationSettingsAccountDashboard, hs.InternalPutUserNotificationSettingsAccountDashboard},
}
addEndpointsToRouters(dashboardSettingsEndpoints, publicDashboardNotificationSettingsRouter, internalDashboardNotificationSettingsRouter)
}
From 6779fe4e8cd3878215d460dbcde29bb9eada120a Mon Sep 17 00:00:00 2001
From: Lucca <109136188+LuccaBitfly@users.noreply.github.com>
Date: Wed, 18 Sep 2024 12:31:50 +0200
Subject: [PATCH 133/187] (BEDS-487) pass user id when updating paired devices
(#865)
---
backend/pkg/api/data_access/dummy.go | 4 ++--
backend/pkg/api/data_access/notifications.go | 12 ++++++------
backend/pkg/api/handlers/public.go | 14 ++++++++++++--
frontend/types/api/archiver.ts | 16 ----------------
frontend/types/api/validator_dashboard.ts | 2 +-
5 files changed, 21 insertions(+), 27 deletions(-)
delete mode 100644 frontend/types/api/archiver.ts
diff --git a/backend/pkg/api/data_access/dummy.go b/backend/pkg/api/data_access/dummy.go
index 1dc80125d..7aef7bc48 100644
--- a/backend/pkg/api/data_access/dummy.go
+++ b/backend/pkg/api/data_access/dummy.go
@@ -482,10 +482,10 @@ func (d *DummyService) UpdateNotificationSettingsGeneral(ctx context.Context, us
func (d *DummyService) UpdateNotificationSettingsNetworks(ctx context.Context, userId uint64, chainId uint64, settings t.NotificationSettingsNetwork) error {
return nil
}
-func (d *DummyService) UpdateNotificationSettingsPairedDevice(ctx context.Context, pairedDeviceId string, name string, IsNotificationsEnabled bool) error {
+func (d *DummyService) UpdateNotificationSettingsPairedDevice(ctx context.Context, userId uint64, pairedDeviceId string, name string, IsNotificationsEnabled bool) error {
return nil
}
-func (d *DummyService) DeleteNotificationSettingsPairedDevice(ctx context.Context, pairedDeviceId string) error {
+func (d *DummyService) DeleteNotificationSettingsPairedDevice(ctx context.Context, userId uint64, pairedDeviceId string) error {
return nil
}
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) {
diff --git a/backend/pkg/api/data_access/notifications.go b/backend/pkg/api/data_access/notifications.go
index 94530d441..5389847db 100644
--- a/backend/pkg/api/data_access/notifications.go
+++ b/backend/pkg/api/data_access/notifications.go
@@ -23,8 +23,8 @@ type NotificationsRepository interface {
GetNotificationSettings(ctx context.Context, userId uint64) (*t.NotificationSettings, error)
UpdateNotificationSettingsGeneral(ctx context.Context, userId uint64, settings t.NotificationSettingsGeneral) error
UpdateNotificationSettingsNetworks(ctx context.Context, userId uint64, chainId uint64, settings t.NotificationSettingsNetwork) error
- UpdateNotificationSettingsPairedDevice(ctx context.Context, pairedDeviceId string, name string, IsNotificationsEnabled bool) error
- DeleteNotificationSettingsPairedDevice(ctx context.Context, pairedDeviceId string) error
+ UpdateNotificationSettingsPairedDevice(ctx context.Context, userId uint64, pairedDeviceId string, name string, IsNotificationsEnabled bool) error
+ DeleteNotificationSettingsPairedDevice(ctx context.Context, userId uint64, pairedDeviceId string) 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
@@ -66,11 +66,11 @@ func (d *DataAccessService) UpdateNotificationSettingsGeneral(ctx context.Contex
func (d *DataAccessService) UpdateNotificationSettingsNetworks(ctx context.Context, userId uint64, chainId uint64, settings t.NotificationSettingsNetwork) error {
return d.dummy.UpdateNotificationSettingsNetworks(ctx, userId, chainId, settings)
}
-func (d *DataAccessService) UpdateNotificationSettingsPairedDevice(ctx context.Context, pairedDeviceId string, name string, IsNotificationsEnabled bool) error {
- return d.dummy.UpdateNotificationSettingsPairedDevice(ctx, pairedDeviceId, name, IsNotificationsEnabled)
+func (d *DataAccessService) UpdateNotificationSettingsPairedDevice(ctx context.Context, userId uint64, pairedDeviceId string, name string, IsNotificationsEnabled bool) error {
+ return d.dummy.UpdateNotificationSettingsPairedDevice(ctx, userId, pairedDeviceId, name, IsNotificationsEnabled)
}
-func (d *DataAccessService) DeleteNotificationSettingsPairedDevice(ctx context.Context, pairedDeviceId string) error {
- return d.dummy.DeleteNotificationSettingsPairedDevice(ctx, pairedDeviceId)
+func (d *DataAccessService) DeleteNotificationSettingsPairedDevice(ctx context.Context, userId uint64, pairedDeviceId string) error {
+ return d.dummy.DeleteNotificationSettingsPairedDevice(ctx, userId, pairedDeviceId)
}
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/public.go b/backend/pkg/api/handlers/public.go
index a31ad6bb2..b3ffd15dd 100644
--- a/backend/pkg/api/handlers/public.go
+++ b/backend/pkg/api/handlers/public.go
@@ -2278,6 +2278,11 @@ func (h *HandlerService) PublicPutUserNotificationSettingsNetworks(w http.Respon
// @Router /users/me/notifications/settings/paired-devices/{paired_device_id} [put]
func (h *HandlerService) PublicPutUserNotificationSettingsPairedDevices(w http.ResponseWriter, r *http.Request) {
var v validationError
+ userId, err := GetUserIdByContext(r)
+ if err != nil {
+ handleErr(w, r, err)
+ return
+ }
type request struct {
Name string `json:"name,omitempty"`
IsNotificationsEnabled bool `json:"is_notifications_enabled"`
@@ -2294,7 +2299,7 @@ func (h *HandlerService) PublicPutUserNotificationSettingsPairedDevices(w http.R
handleErr(w, r, v)
return
}
- err := h.dai.UpdateNotificationSettingsPairedDevice(r.Context(), pairedDeviceId, name, req.IsNotificationsEnabled)
+ err = h.dai.UpdateNotificationSettingsPairedDevice(r.Context(), userId, pairedDeviceId, name, req.IsNotificationsEnabled)
if err != nil {
handleErr(w, r, err)
return
@@ -2323,13 +2328,18 @@ func (h *HandlerService) PublicPutUserNotificationSettingsPairedDevices(w http.R
// @Router /users/me/notifications/settings/paired-devices/{paired_device_id} [delete]
func (h *HandlerService) PublicDeleteUserNotificationSettingsPairedDevices(w http.ResponseWriter, r *http.Request) {
var v validationError
+ userId, err := GetUserIdByContext(r)
+ if err != nil {
+ handleErr(w, r, err)
+ return
+ }
// TODO use a better way to validate the paired device id
pairedDeviceId := v.checkRegex(reNonEmpty, mux.Vars(r)["paired_device_id"], "paired_device_id")
if v.hasErrors() {
handleErr(w, r, v)
return
}
- err := h.dai.DeleteNotificationSettingsPairedDevice(r.Context(), pairedDeviceId)
+ err = h.dai.DeleteNotificationSettingsPairedDevice(r.Context(), userId, pairedDeviceId)
if err != nil {
handleErr(w, r, err)
return
diff --git a/frontend/types/api/archiver.ts b/frontend/types/api/archiver.ts
deleted file mode 100644
index a0ecfe481..000000000
--- a/frontend/types/api/archiver.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-// Code generated by tygo. DO NOT EDIT.
-/* eslint-disable */
-
-//////////
-// source: archiver.go
-
-export interface ArchiverDashboard {
- DashboardId: number /* uint64 */;
- IsArchived: boolean;
- GroupCount: number /* uint64 */;
- ValidatorCount: number /* uint64 */;
-}
-export interface ArchiverDashboardArchiveReason {
- DashboardId: number /* uint64 */;
- ArchivedReason: any /* enums.VDBArchivedReason */;
-}
diff --git a/frontend/types/api/validator_dashboard.ts b/frontend/types/api/validator_dashboard.ts
index 10fb9d3e1..03f07f548 100644
--- a/frontend/types/api/validator_dashboard.ts
+++ b/frontend/types/api/validator_dashboard.ts
@@ -160,8 +160,8 @@ export interface VDBBlocksTableRow {
group_id: number /* uint64 */;
epoch: number /* uint64 */;
slot: number /* uint64 */;
- status: 'success' | 'missed' | 'orphaned' | 'scheduled';
block?: number /* uint64 */;
+ status: 'success' | 'missed' | 'orphaned' | 'scheduled';
reward_recipient?: Address;
reward?: ClElValue;
graffiti?: string;
From 49359ba42b76807335ace7275843885e67760ed2 Mon Sep 17 00:00:00 2001
From: remoterami <142154971+remoterami@users.noreply.github.com>
Date: Wed, 18 Sep 2024 12:39:12 +0200
Subject: [PATCH 134/187] (BEDS-155) Data Access: adjusted address struct
(#733)
* adjusted address struct
* adding contract data (WIP)
* added contract status
* retrieving address details (label, ens, ...) from single method
* writing labels to result, migration fixed
* CR feedback
* renamed tags to names
---
backend/pkg/api/data_access/general.go | 48 ++++++++
backend/pkg/api/data_access/vdb_blocks.go | 27 ++++-
backend/pkg/api/data_access/vdb_deposits.go | 41 ++++++-
.../pkg/api/data_access/vdb_withdrawals.go | 65 ++++++++---
backend/pkg/api/types/common.go | 6 +-
backend/pkg/commons/db/bigtable_eth1.go | 106 +++++++++---------
backend/pkg/commons/db/ens.go | 10 +-
.../20240822134034_add_address_tags.sql | 15 +++
frontend/types/api/common.ts | 2 +
9 files changed, 239 insertions(+), 81 deletions(-)
create mode 100644 backend/pkg/api/data_access/general.go
create mode 100644 backend/pkg/commons/db/migrations/postgres/20240822134034_add_address_tags.sql
diff --git a/backend/pkg/api/data_access/general.go b/backend/pkg/api/data_access/general.go
new file mode 100644
index 000000000..7debc5dfc
--- /dev/null
+++ b/backend/pkg/api/data_access/general.go
@@ -0,0 +1,48 @@
+package dataaccess
+
+import (
+ "context"
+
+ "github.com/ethereum/go-ethereum/common/hexutil"
+ "github.com/gobitfly/beaconchain/pkg/api/types"
+ "github.com/gobitfly/beaconchain/pkg/commons/db"
+)
+
+// retrieve (primary) ens name and optional name (=label) maintained by beaconcha.in, if present
+func (d *DataAccessService) GetNamesAndEnsForAddresses(ctx context.Context, addressMap map[string]*types.Address) error {
+ addresses := make([][]byte, 0, len(addressMap))
+ ensMapping := make(map[string]string, len(addressMap))
+ for address, data := range addressMap {
+ ensMapping[address] = ""
+ add, err := hexutil.Decode(address)
+ if err != nil {
+ return err
+ }
+ addresses = append(addresses, add)
+ if data == nil {
+ addressMap[address] = &types.Address{Hash: types.Hash(address)}
+ }
+ }
+ // determine ENS names
+ if err := db.GetEnsNamesForAddresses(ensMapping); err != nil {
+ return err
+ }
+ for address, ens := range ensMapping {
+ addressMap[address].Ens = ens
+ }
+
+ // determine names
+ names := []struct {
+ Address []byte `db:"address"`
+ Name string `db:"name"`
+ }{}
+ err := d.alloyReader.SelectContext(ctx, &names, `SELECT address, name FROM address_names WHERE address = ANY($1)`, addresses)
+ if err != nil {
+ return err
+ }
+
+ for _, name := range names {
+ addressMap[hexutil.Encode(name.Address)].Label = name.Name
+ }
+ return nil
+}
diff --git a/backend/pkg/api/data_access/vdb_blocks.go b/backend/pkg/api/data_access/vdb_blocks.go
index 3780599b7..2a2a0953a 100644
--- a/backend/pkg/api/data_access/vdb_blocks.go
+++ b/backend/pkg/api/data_access/vdb_blocks.go
@@ -15,6 +15,7 @@ import (
"github.com/gobitfly/beaconchain/pkg/commons/cache"
"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/shopspring/decimal"
)
@@ -345,7 +346,8 @@ func (d *DataAccessService) GetValidatorDashboardBlocks(ctx context.Context, das
}
data := make([]t.VDBBlocksTableRow, len(proposals))
- ensMapping := make(map[string]string)
+ addressMapping := make(map[string]*t.Address)
+ contractStatusRequests := make([]db.ContractInteractionAtRequest, 0, len(proposals))
for i, proposal := range proposals {
data[i].GroupId = proposal.Group
if dashboardId.AggregateGroups {
@@ -382,7 +384,13 @@ func (d *DataAccessService) GetValidatorDashboardBlocks(ctx context.Context, das
Hash: t.Hash(hexutil.Encode(proposal.FeeRecipient)),
}
data[i].RewardRecipient = &rewardRecp
- ensMapping[hexutil.Encode(proposal.FeeRecipient)] = ""
+ addressMapping[hexutil.Encode(proposal.FeeRecipient)] = nil
+ contractStatusRequests = append(contractStatusRequests, db.ContractInteractionAtRequest{
+ Address: fmt.Sprintf("%x", proposal.FeeRecipient),
+ Block: proposal.Block.Int64,
+ TxIdx: -1,
+ TraceIdx: -1,
+ })
reward.El = proposal.ElReward.Decimal.Mul(decimal.NewFromInt(1e18))
}
if proposal.ClReward.Valid {
@@ -393,13 +401,22 @@ func (d *DataAccessService) GetValidatorDashboardBlocks(ctx context.Context, das
}
// determine reward recipient ENS names
startTime = time.Now()
- if err := db.GetEnsNamesForAddresses(ensMapping); err != nil {
+ // determine ens/names
+ if err := d.GetNamesAndEnsForAddresses(ctx, addressMapping); err != nil {
return nil, nil, err
}
- log.Debugf("=== getting ens names took %s", time.Since(startTime))
+ log.Debugf("=== getting ens + labels names took %s", time.Since(startTime))
+ // determine contract statuses
+ contractStatuses, err := d.bigtable.GetAddressContractInteractionsAt(contractStatusRequests)
+ if err != nil {
+ return nil, nil, err
+ }
+ var contractIdx int
for i := range data {
if data[i].RewardRecipient != nil {
- data[i].RewardRecipient.Ens = ensMapping[string(data[i].RewardRecipient.Hash)]
+ data[i].RewardRecipient = addressMapping[string(data[i].RewardRecipient.Hash)]
+ data[i].RewardRecipient.IsContract = contractStatuses[contractIdx] == types.CONTRACT_CREATION || contractStatuses[contractIdx] == types.CONTRACT_PRESENT
+ contractIdx += 1
}
}
if !moreDataFlag && !currentCursor.IsValid() {
diff --git a/backend/pkg/api/data_access/vdb_deposits.go b/backend/pkg/api/data_access/vdb_deposits.go
index 7d32cc002..8c1d49003 100644
--- a/backend/pkg/api/data_access/vdb_deposits.go
+++ b/backend/pkg/api/data_access/vdb_deposits.go
@@ -14,6 +14,7 @@ import (
"github.com/gobitfly/beaconchain/pkg/api/enums"
t "github.com/gobitfly/beaconchain/pkg/api/types"
"github.com/gobitfly/beaconchain/pkg/commons/db"
+ "github.com/gobitfly/beaconchain/pkg/commons/types"
"github.com/gobitfly/beaconchain/pkg/commons/utils"
"github.com/lib/pq"
"github.com/shopspring/decimal"
@@ -121,17 +122,27 @@ func (d *DataAccessService) GetValidatorDashboardElDeposits(ctx context.Context,
}
responseData := make([]t.VDBExecutionDepositsTableRow, len(data))
+ addressMapping := make(map[string]*t.Address)
+ fromContractStatusRequests := make([]db.ContractInteractionAtRequest, len(data))
+ depositorContractStatusRequests := make([]db.ContractInteractionAtRequest, 0, len(data))
for i, row := range data {
responseData[i] = t.VDBExecutionDepositsTableRow{
PublicKey: t.PubKey(pubkeys[i]),
Block: uint64(row.BlockNumber),
Timestamp: row.Timestamp.Unix(),
- From: t.Address{Hash: t.Hash(hexutil.Encode(row.From))},
TxHash: t.Hash(hexutil.Encode(row.TxHash)),
WithdrawalCredential: t.Hash(hexutil.Encode(row.WithdrawalCredentials)),
Amount: utils.GWeiToWei(big.NewInt(row.Amount)),
Valid: row.Valid,
}
+ addressMapping[hexutil.Encode(row.From)] = nil
+ fromContractStatusRequests[i] = db.ContractInteractionAtRequest{
+ Address: fmt.Sprintf("%x", row.From),
+ Block: row.BlockNumber,
+ // TODO not entirely correct, would need to determine tx index and itx index of tx. But good enough for now
+ TxIdx: -1,
+ TraceIdx: -1,
+ }
if row.GroupId.Valid {
if dashboardId.AggregateGroups {
responseData[i].GroupId = t.DefaultGroupId
@@ -143,6 +154,10 @@ func (d *DataAccessService) GetValidatorDashboardElDeposits(ctx context.Context,
}
if len(row.Depositor) > 0 {
responseData[i].Depositor = t.Address{Hash: t.Hash(hexutil.Encode(row.Depositor))}
+ addressMapping[hexutil.Encode(row.Depositor)] = nil
+ depositorReq := fromContractStatusRequests[i]
+ depositorReq.Address = fmt.Sprintf("%x", row.Depositor)
+ depositorContractStatusRequests = append(depositorContractStatusRequests, depositorReq)
} else {
responseData[i].Depositor = responseData[i].From
}
@@ -150,6 +165,30 @@ func (d *DataAccessService) GetValidatorDashboardElDeposits(ctx context.Context,
responseData[i].Index = &v
}
}
+
+ // populate address data
+ if err := d.GetNamesAndEnsForAddresses(ctx, addressMapping); err != nil {
+ return nil, nil, err
+ }
+ fromContractStatuses, err := d.bigtable.GetAddressContractInteractionsAt(fromContractStatusRequests)
+ if err != nil {
+ return nil, nil, err
+ }
+ depositorContractStatuses, err := d.bigtable.GetAddressContractInteractionsAt(depositorContractStatusRequests)
+ if err != nil {
+ return nil, nil, err
+ }
+ var depositorIdx int
+ for i := range data {
+ responseData[i].From = *addressMapping[string(responseData[i].From.Hash)]
+ responseData[i].From.IsContract = fromContractStatuses[i] == types.CONTRACT_CREATION || fromContractStatuses[i] == types.CONTRACT_PRESENT
+ responseData[i].Depositor.IsContract = responseData[i].From.IsContract
+ if responseData[i].Depositor.Hash != responseData[i].From.Hash {
+ responseData[i].Depositor.IsContract = depositorContractStatuses[depositorIdx] == types.CONTRACT_CREATION || depositorContractStatuses[depositorIdx] == types.CONTRACT_PRESENT
+ depositorIdx += 1
+ }
+ }
+
var paging t.Paging
moreDataFlag := len(responseData) > int(limit)
diff --git a/backend/pkg/api/data_access/vdb_withdrawals.go b/backend/pkg/api/data_access/vdb_withdrawals.go
index c3837f6f0..21a190f44 100644
--- a/backend/pkg/api/data_access/vdb_withdrawals.go
+++ b/backend/pkg/api/data_access/vdb_withdrawals.go
@@ -17,6 +17,7 @@ import (
t "github.com/gobitfly/beaconchain/pkg/api/types"
"github.com/gobitfly/beaconchain/pkg/commons/cache"
"github.com/gobitfly/beaconchain/pkg/commons/db"
+ "github.com/gobitfly/beaconchain/pkg/commons/types"
"github.com/gobitfly/beaconchain/pkg/commons/utils"
"github.com/lib/pq"
"github.com/pkg/errors"
@@ -111,6 +112,7 @@ func (d *DataAccessService) GetValidatorDashboardWithdrawals(ctx context.Context
// Get the withdrawals for the validators
queryResult := []struct {
BlockSlot uint64 `db:"block_slot"`
+ BlockNumber uint64 `db:"exec_block_number"`
WithdrawalIndex uint64 `db:"withdrawalindex"`
ValidatorIndex uint64 `db:"validatorindex"`
Address []byte `db:"address"`
@@ -121,6 +123,7 @@ func (d *DataAccessService) GetValidatorDashboardWithdrawals(ctx context.Context
withdrawalsQuery := `
SELECT
w.block_slot,
+ b.exec_block_number,
w.withdrawalindex,
w.validatorindex,
w.address,
@@ -196,32 +199,43 @@ func (d *DataAccessService) GetValidatorDashboardWithdrawals(ctx context.Context
}
// Prepare the ENS map
- addressEns := make(map[string]string)
- for _, withdrawal := range queryResult {
+ addressMapping := make(map[string]*t.Address)
+ contractStatusRequests := make([]db.ContractInteractionAtRequest, len(queryResult))
+ for i, withdrawal := range queryResult {
address := hexutil.Encode(withdrawal.Address)
- addressEns[address] = ""
+ addressMapping[address] = nil
+ contractStatusRequests[i] = db.ContractInteractionAtRequest{
+ Address: fmt.Sprintf("%x", withdrawal.Address),
+ Block: int64(withdrawal.BlockNumber),
+ TxIdx: -1,
+ TraceIdx: -1,
+ }
+ }
+
+ // Get the ENS names and (label) names for the addresses
+ if err := d.GetNamesAndEnsForAddresses(ctx, addressMapping); err != nil {
+ return nil, nil, err
}
- // Get the ENS names for the addresses
- if err := db.GetEnsNamesForAddresses(addressEns); err != nil {
+ // Get the contract status for the addresses
+ contractStatuses, err := d.bigtable.GetAddressContractInteractionsAt(contractStatusRequests)
+ if err != nil {
return nil, nil, err
}
// Create the result
cursorData := make([]t.WithdrawalsCursor, 0)
- for _, withdrawal := range queryResult {
+ for i, withdrawal := range queryResult {
address := hexutil.Encode(withdrawal.Address)
result = append(result, t.VDBWithdrawalsTableRow{
- Epoch: withdrawal.BlockSlot / utils.Config.Chain.ClConfig.SlotsPerEpoch,
- Slot: withdrawal.BlockSlot,
- Index: withdrawal.ValidatorIndex,
- GroupId: validatorGroupMap[withdrawal.ValidatorIndex],
- Recipient: t.Address{
- Hash: t.Hash(address),
- Ens: addressEns[address],
- },
- Amount: utils.GWeiToWei(big.NewInt(int64(withdrawal.Amount))),
+ Epoch: withdrawal.BlockSlot / utils.Config.Chain.ClConfig.SlotsPerEpoch,
+ Slot: withdrawal.BlockSlot,
+ Index: withdrawal.ValidatorIndex,
+ Recipient: *addressMapping[address],
+ GroupId: validatorGroupMap[withdrawal.ValidatorIndex],
+ Amount: utils.GWeiToWei(big.NewInt(int64(withdrawal.Amount))),
})
+ result[i].Recipient.IsContract = contractStatuses[i] == types.CONTRACT_CREATION || contractStatuses[i] == types.CONTRACT_PRESENT
cursorData = append(cursorData, t.WithdrawalsCursor{
Slot: withdrawal.BlockSlot,
WithdrawalIndex: withdrawal.WithdrawalIndex,
@@ -256,7 +270,8 @@ func (d *DataAccessService) GetValidatorDashboardWithdrawals(ctx context.Context
if nextData != nil {
// Complete the next data
nextData.GroupId = validatorGroupMap[nextData.Index]
- nextData.Recipient.Ens = addressEns[string(nextData.Recipient.Hash)]
+ // TODO integrate label/ens data for "next" row
+ // nextData.Recipient.Ens = addressEns[string(nextData.Recipient.Hash)]
} else {
// If there is no next data, add a missing estimate row
nextData = &t.VDBWithdrawalsTableRow{
@@ -393,12 +408,28 @@ func (d *DataAccessService) getNextWithdrawalRow(queryValidators []t.VDBValidato
withdrawalAmount = 0
}
+ ens_name, err := db.GetEnsNameForAddress(*address, utils.SlotToTime(nextWithdrawalSlot))
+ if err != sql.ErrNoRows {
+ return nil, err
+ }
+
+ contractStatusReq := []db.ContractInteractionAtRequest{{
+ Address: fmt.Sprintf("%x", address),
+ Block: -1,
+ }}
+ contractStatus, err := d.bigtable.GetAddressContractInteractionsAt(contractStatusReq)
+ if err != nil {
+ return nil, err
+ }
+
nextData := &t.VDBWithdrawalsTableRow{
Epoch: nextWithdrawalSlot / utils.Config.Chain.ClConfig.SlotsPerEpoch,
Slot: nextWithdrawalSlot,
Index: *nextValidator,
Recipient: t.Address{
- Hash: t.Hash(address.String()),
+ Hash: t.Hash(address.String()),
+ Ens: ens_name,
+ IsContract: contractStatus[0] == types.CONTRACT_CREATION || contractStatus[0] == types.CONTRACT_PRESENT,
},
Amount: utils.GWeiToWei(big.NewInt(int64(withdrawalAmount))),
}
diff --git a/backend/pkg/api/types/common.go b/backend/pkg/api/types/common.go
index 2c7253623..2deebb1b9 100644
--- a/backend/pkg/api/types/common.go
+++ b/backend/pkg/api/types/common.go
@@ -34,8 +34,10 @@ type PubKey string
type Hash string // blocks, txs etc.
type Address struct {
- Hash Hash `json:"hash"`
- Ens string `json:"ens,omitempty"`
+ Hash Hash `json:"hash"`
+ IsContract bool `json:"is_contract"`
+ Ens string `json:"ens,omitempty"`
+ Label string `json:"label,omitempty"`
}
type LuckItem struct {
diff --git a/backend/pkg/commons/db/bigtable_eth1.go b/backend/pkg/commons/db/bigtable_eth1.go
index e2fca3052..3861374f2 100644
--- a/backend/pkg/commons/db/bigtable_eth1.go
+++ b/backend/pkg/commons/db/bigtable_eth1.go
@@ -3138,7 +3138,7 @@ func (bigtable *Bigtable) GetAddressName(address []byte) (string, error) {
add := common.Address{}
add.SetBytes(address)
- name, err := GetEnsNameForAddress(add)
+ name, err := GetEnsNameForAddress(add, time.Time{})
if err == nil && len(name) > 0 {
return name, nil
}
@@ -3215,11 +3215,11 @@ type isContractInfo struct {
ts gcp_bigtable.Timestamp
}
-type contractInteractionAtRequest struct {
- address string
- block int64
- txIdx int64
- traceIdx int64
+type ContractInteractionAtRequest struct {
+ Address string // expected all lowercase without 0x prefix
+ Block int64
+ TxIdx int64
+ TraceIdx int64
}
func (bigtable *Bigtable) getAddressIsContractHistories(histories map[string][]isContractInfo) error {
@@ -3270,7 +3270,7 @@ func (bigtable *Bigtable) getAddressIsContractHistories(histories map[string][]i
// returns account state after the given execution state
// -1 is latest (e.g. "txIdx" = -1 returns the contract state after execution of "block", "block" = -1 returns the state at chain head)
-func (bigtable *Bigtable) GetAddressContractInteractionsAt(requests []contractInteractionAtRequest) ([]types.ContractInteractionType, error) {
+func (bigtable *Bigtable) GetAddressContractInteractionsAt(requests []ContractInteractionAtRequest) ([]types.ContractInteractionType, error) {
results := make([]types.ContractInteractionType, len(requests))
if len(requests) == 0 {
return results, nil
@@ -3279,7 +3279,7 @@ func (bigtable *Bigtable) GetAddressContractInteractionsAt(requests []contractIn
// get histories
histories := make(map[string][]isContractInfo, len(requests))
for _, request := range requests {
- histories[request.address] = nil
+ histories[request.Address] = nil
}
err := bigtable.getAddressIsContractHistories(histories)
if err != nil {
@@ -3288,22 +3288,22 @@ func (bigtable *Bigtable) GetAddressContractInteractionsAt(requests []contractIn
// evaluate requests; CONTRACT_NONE is default
for i, request := range requests {
- history, ok := histories[request.address]
+ history, ok := histories[request.Address]
if !ok || history == nil || len(history) == 0 {
continue
}
latestUpdateIdxBeforeReq := 0
- if request.block != -1 {
+ if request.Block != -1 {
var block, tx, itx uint64
- if request.txIdx == -1 {
- block = uint64(request.block + 1)
- } else if request.traceIdx == -1 {
- block = uint64(request.block)
- tx = uint64(request.txIdx + 1)
+ if request.TxIdx == -1 {
+ block = uint64(request.Block + 1)
+ } else if request.TraceIdx == -1 {
+ block = uint64(request.Block)
+ tx = uint64(request.TxIdx + 1)
} else {
- block = uint64(request.block)
- tx = uint64(request.txIdx)
- itx = uint64(request.traceIdx + 1)
+ block = uint64(request.Block)
+ tx = uint64(request.TxIdx)
+ itx = uint64(request.TraceIdx + 1)
}
req_ts, err := encodeIsContractUpdateTs(block, tx, itx)
if err != nil {
@@ -3319,7 +3319,7 @@ func (bigtable *Bigtable) GetAddressContractInteractionsAt(requests []contractIn
}
b, tx, trace := decodeIsContractUpdateTs(history[latestUpdateIdxBeforeReq].ts)
- exact_match := request.block == -1 || request.block == int64(b) && (request.txIdx == -1 || request.txIdx == int64(tx) && (request.traceIdx == -1 || request.traceIdx == int64(trace)))
+ exact_match := request.Block == -1 || request.Block == int64(b) && (request.TxIdx == -1 || request.TxIdx == int64(tx) && (request.TraceIdx == -1 || request.TraceIdx == int64(trace)))
if exact_match {
results[i] = types.CONTRACT_DESTRUCTION
@@ -3343,17 +3343,17 @@ func (bigtable *Bigtable) GetAddressContractInteractionsAt(requests []contractIn
// convenience function to get contract interaction status per transaction of a block
func (bigtable *Bigtable) GetAddressContractInteractionsAtBlock(block *types.Eth1Block) ([]types.ContractInteractionType, error) {
- requests := make([]contractInteractionAtRequest, len(block.GetTransactions()))
+ requests := make([]ContractInteractionAtRequest, len(block.GetTransactions()))
for i, tx := range block.GetTransactions() {
address := tx.GetTo()
if len(address) == 0 {
address = tx.GetContractAddress()
}
- requests[i] = contractInteractionAtRequest{
- address: fmt.Sprintf("%x", address),
- block: int64(block.GetNumber()),
- txIdx: int64(i),
- traceIdx: -1,
+ requests[i] = ContractInteractionAtRequest{
+ Address: fmt.Sprintf("%x", address),
+ Block: int64(block.GetNumber()),
+ TxIdx: int64(i),
+ TraceIdx: -1,
}
}
@@ -3363,19 +3363,19 @@ func (bigtable *Bigtable) GetAddressContractInteractionsAtBlock(block *types.Eth
// convenience function to get contract interaction status per subtransaction of a transaction
// 2nd parameter specifies [tx_idx, trace_idx] for each internal tx
func (bigtable *Bigtable) GetAddressContractInteractionsAtITransactions(itransactions []*types.Eth1InternalTransactionIndexed, idxs [][2]int64) ([][2]types.ContractInteractionType, error) {
- requests := make([]contractInteractionAtRequest, 0, len(itransactions)*2)
+ requests := make([]ContractInteractionAtRequest, 0, len(itransactions)*2)
for i, tx := range itransactions {
- requests = append(requests, contractInteractionAtRequest{
- address: fmt.Sprintf("%x", tx.GetFrom()),
- block: int64(tx.GetBlockNumber()),
- txIdx: idxs[i][0],
- traceIdx: idxs[i][1],
+ requests = append(requests, ContractInteractionAtRequest{
+ Address: fmt.Sprintf("%x", tx.GetFrom()),
+ Block: int64(tx.GetBlockNumber()),
+ TxIdx: idxs[i][0],
+ TraceIdx: idxs[i][1],
})
- requests = append(requests, contractInteractionAtRequest{
- address: fmt.Sprintf("%x", tx.GetTo()),
- block: int64(tx.GetBlockNumber()),
- txIdx: idxs[i][0],
- traceIdx: idxs[i][1],
+ requests = append(requests, ContractInteractionAtRequest{
+ Address: fmt.Sprintf("%x", tx.GetTo()),
+ Block: int64(tx.GetBlockNumber()),
+ TxIdx: idxs[i][0],
+ TraceIdx: idxs[i][1],
})
}
results, err := bigtable.GetAddressContractInteractionsAt(requests)
@@ -3392,20 +3392,20 @@ func (bigtable *Bigtable) GetAddressContractInteractionsAtITransactions(itransac
// convenience function to get contract interaction status per parity trace
func (bigtable *Bigtable) GetAddressContractInteractionsAtParityTraces(traces []*rpc.ParityTraceResult) ([][2]types.ContractInteractionType, error) {
- requests := make([]contractInteractionAtRequest, 0, len(traces)*2)
+ requests := make([]ContractInteractionAtRequest, 0, len(traces)*2)
for i, itx := range traces {
from, to, _, _ := itx.ConvertFields()
- requests = append(requests, contractInteractionAtRequest{
- address: fmt.Sprintf("%x", from),
- block: int64(itx.BlockNumber),
- txIdx: int64(itx.TransactionPosition),
- traceIdx: int64(i),
+ requests = append(requests, ContractInteractionAtRequest{
+ Address: fmt.Sprintf("%x", from),
+ Block: int64(itx.BlockNumber),
+ TxIdx: int64(itx.TransactionPosition),
+ TraceIdx: int64(i),
})
- requests = append(requests, contractInteractionAtRequest{
- address: fmt.Sprintf("%x", to),
- block: int64(itx.BlockNumber),
- txIdx: int64(itx.TransactionPosition),
- traceIdx: int64(i),
+ requests = append(requests, ContractInteractionAtRequest{
+ Address: fmt.Sprintf("%x", to),
+ Block: int64(itx.BlockNumber),
+ TxIdx: int64(itx.TransactionPosition),
+ TraceIdx: int64(i),
})
}
results, err := bigtable.GetAddressContractInteractionsAt(requests)
@@ -3422,13 +3422,13 @@ func (bigtable *Bigtable) GetAddressContractInteractionsAtParityTraces(traces []
// convenience function to get contract interaction status per transaction
func (bigtable *Bigtable) GetAddressContractInteractionsAtTransactions(transactions []*types.Eth1TransactionIndexed, idxs []int64) ([]types.ContractInteractionType, error) {
- requests := make([]contractInteractionAtRequest, len(transactions))
+ requests := make([]ContractInteractionAtRequest, len(transactions))
for i, tx := range transactions {
- requests[i] = contractInteractionAtRequest{
- address: fmt.Sprintf("%x", tx.GetTo()),
- block: int64(tx.GetBlockNumber()),
- txIdx: idxs[i],
- traceIdx: -1,
+ requests[i] = ContractInteractionAtRequest{
+ Address: fmt.Sprintf("%x", tx.GetTo()),
+ Block: int64(tx.GetBlockNumber()),
+ TxIdx: idxs[i],
+ TraceIdx: -1,
}
}
return bigtable.GetAddressContractInteractionsAt(requests)
diff --git a/backend/pkg/commons/db/ens.go b/backend/pkg/commons/db/ens.go
index 46001df56..5020f31e6 100644
--- a/backend/pkg/commons/db/ens.go
+++ b/backend/pkg/commons/db/ens.go
@@ -609,15 +609,19 @@ func GetAddressForEnsName(name string) (address *common.Address, err error) {
return address, err
}
-func GetEnsNameForAddress(address common.Address) (name string, err error) {
+// pass invalid time to get latest data
+func GetEnsNameForAddress(address common.Address, validUntil time.Time) (name string, err error) {
+ if validUntil.IsZero() {
+ validUntil = time.Now()
+ }
err = ReaderDb.Get(&name, `
SELECT ens_name
FROM ens
WHERE
address = $1 AND
is_primary_name AND
- valid_to >= now()
- ;`, address.Bytes())
+ valid_to >= $2
+ ;`, address.Bytes(), validUntil)
return name, err
}
diff --git a/backend/pkg/commons/db/migrations/postgres/20240822134034_add_address_tags.sql b/backend/pkg/commons/db/migrations/postgres/20240822134034_add_address_tags.sql
new file mode 100644
index 000000000..3579f7ef6
--- /dev/null
+++ b/backend/pkg/commons/db/migrations/postgres/20240822134034_add_address_tags.sql
@@ -0,0 +1,15 @@
+-- +goose Up
+-- +goose StatementBegin
+SELECT('up SQL query - create address_names table');
+CREATE TABLE IF NOT EXISTS address_names (
+ address bytea NOT NULL UNIQUE,
+ name TEXT NOT NULL,
+ PRIMARY KEY (address, name)
+);
+-- +goose StatementEnd
+
+-- +goose Down
+-- +goose StatementBegin
+SELECT('down SQL query - drop address_names table');
+DROP TABLE IF EXISTS address_names;
+-- +goose StatementEnd
diff --git a/frontend/types/api/common.ts b/frontend/types/api/common.ts
index ecce7231e..3c8c9506a 100644
--- a/frontend/types/api/common.ts
+++ b/frontend/types/api/common.ts
@@ -27,7 +27,9 @@ export type PubKey = string;
export type Hash = string; // blocks, txs etc.
export interface Address {
hash: Hash;
+ is_contract: boolean;
ens?: string;
+ label?: string;
}
export interface LuckItem {
percent: number /* float64 */;
From c55824620a7a396b8cc637d751cda4a64ff6f31a Mon Sep 17 00:00:00 2001
From: Patrick
Date: Wed, 18 Sep 2024 12:50:22 +0200
Subject: [PATCH 135/187] fix(eth1indexer): fix handling of specific error
(#687) (BEDS-90)
* eth1indexer: less logging on ens
* eth1indexer: fix handling of specific error
---
backend/pkg/commons/db/ens.go | 22 +++++++++++-----------
1 file changed, 11 insertions(+), 11 deletions(-)
diff --git a/backend/pkg/commons/db/ens.go b/backend/pkg/commons/db/ens.go
index 5020f31e6..119f6c00b 100644
--- a/backend/pkg/commons/db/ens.go
+++ b/backend/pkg/commons/db/ens.go
@@ -436,7 +436,7 @@ func validateEnsAddress(client *ethclient.Client, address common.Address, alread
err.Error() == "no resolution" ||
err.Error() == "execution reverted" ||
strings.HasPrefix(err.Error(), "name is not valid") {
- log.Warnf("reverse resolving address [%v] resulted in a skippable error [%s], skipping it", address, err.Error())
+ // log.Warnf("reverse resolving address [%v] resulted in a skippable error [%s], skipping it", address, err.Error())
} else {
return fmt.Errorf("error could not reverse resolve address [%v]: %w", address, err)
}
@@ -475,7 +475,7 @@ func validateEnsName(client *ethclient.Client, name string, alreadyChecked *EnsC
nameHash, err := go_ens.NameHash(name)
if err != nil {
- log.Warnf("error could not hash name [%v]: %v -> removing ens entry", name, err)
+ // log.Warnf("error could not hash name [%v]: %v -> removing ens entry", name, err)
err = removeEnsName(name)
if err != nil {
return fmt.Errorf("error removing ens name [%v]: %w", name, err)
@@ -488,12 +488,12 @@ func validateEnsName(client *ethclient.Client, name string, alreadyChecked *EnsC
if err.Error() == "unregistered name" ||
err.Error() == "no address" ||
err.Error() == "no resolver" ||
- err.Error() == "abi: attempting to unmarshall an empty string while arguments are expected" ||
+ err.Error() == "abi: attempting to unmarshal an empty string while arguments are expected" ||
strings.Contains(err.Error(), "execution reverted") ||
err.Error() == "invalid jump destination" ||
err.Error() == "invalid opcode: INVALID" {
// the given name is not available anymore or resolving it did not work properly => we can remove it from the db (if it is there)
- log.Warnf("could not resolve name [%v]: %v -> removing ens entry", name, err)
+ // log.Warnf("could not resolve name [%v]: %v -> removing ens entry", name, err)
err = removeEnsName(name)
if err != nil {
return fmt.Errorf("error removing ens name after resolve failed [%v]: %w", name, err)
@@ -516,7 +516,7 @@ func validateEnsName(client *ethclient.Client, name string, alreadyChecked *EnsC
reverseName, err := go_ens.ReverseResolve(client, addr)
if err != nil {
if err.Error() == "not a resolver" || err.Error() == "no resolution" || err.Error() == "execution reverted" {
- log.Warnf("reverse resolving address [%v] for name [%v] resulted in an error [%s], marking entry as not primary", addr, name, err.Error())
+ // log.Warnf("reverse resolving address [%v] for name [%v] resulted in an error [%s], marking entry as not primary", addr, name, err.Error())
} else {
return fmt.Errorf("error could not reverse resolve address [%v]: %w", addr, err)
}
@@ -549,12 +549,12 @@ func validateEnsName(client *ethclient.Client, name string, alreadyChecked *EnsC
return fmt.Errorf("error writing ens data for name [%v]: %w", name, err)
}
- log.InfoWithFields(log.Fields{
- "name": name,
- "address": addr,
- "expires": expires,
- "reverseName": reverseName,
- }, "validated ens name")
+ // log.InfoWithFields(log.Fields{
+ // "name": name,
+ // "address": addr,
+ // "expires": expires,
+ // "reverseName": reverseName,
+ // }, "validated ens name")
return nil
}
From 685d019444c8b3f813c8d12b17f3e090374df9a9 Mon Sep 17 00:00:00 2001
From: Patrick
Date: Wed, 18 Sep 2024 14:13:52 +0200
Subject: [PATCH 136/187] fix(api): fix GetValidatorDashboardElDeposits (#866)
BEDS-155
---
backend/pkg/api/data_access/vdb_deposits.go | 1 +
1 file changed, 1 insertion(+)
diff --git a/backend/pkg/api/data_access/vdb_deposits.go b/backend/pkg/api/data_access/vdb_deposits.go
index 8c1d49003..db2026a23 100644
--- a/backend/pkg/api/data_access/vdb_deposits.go
+++ b/backend/pkg/api/data_access/vdb_deposits.go
@@ -134,6 +134,7 @@ func (d *DataAccessService) GetValidatorDashboardElDeposits(ctx context.Context,
WithdrawalCredential: t.Hash(hexutil.Encode(row.WithdrawalCredentials)),
Amount: utils.GWeiToWei(big.NewInt(row.Amount)),
Valid: row.Valid,
+ From: t.Address{Hash: t.Hash(hexutil.Encode(row.From))},
}
addressMapping[hexutil.Encode(row.From)] = nil
fromContractStatusRequests[i] = db.ContractInteractionAtRequest{
From b261d85f4b704e62e476de709df193911be3d5d2 Mon Sep 17 00:00:00 2001
From: Lucca <109136188+LuccaBitfly@users.noreply.github.com>
Date: Thu, 19 Sep 2024 09:18:43 +0200
Subject: [PATCH 137/187] (BEDS-480) add vdb mobile widget endpoint (#869)
---
backend/pkg/api/data_access/dummy.go | 8 +++--
backend/pkg/api/data_access/vdb.go | 2 ++
backend/pkg/api/data_access/vdb_management.go | 6 ++++
backend/pkg/api/handlers/common.go | 13 +++++---
backend/pkg/api/handlers/internal.go | 33 +++++++++++++++++++
backend/pkg/api/handlers/public.go | 10 +++++-
backend/pkg/api/router.go | 1 +
backend/pkg/api/types/common.go | 8 +++++
backend/pkg/api/types/dashboard.go | 16 ++++-----
backend/pkg/api/types/mobile.go | 15 +++++++++
backend/pkg/api/types/validator_dashboard.go | 9 +----
frontend/types/api/common.ts | 7 ++++
frontend/types/api/mobile.ts | 13 +++++++-
frontend/types/api/validator_dashboard.ts | 15 ++-------
14 files changed, 117 insertions(+), 39 deletions(-)
diff --git a/backend/pkg/api/data_access/dummy.go b/backend/pkg/api/data_access/dummy.go
index 7aef7bc48..7bee9a38c 100644
--- a/backend/pkg/api/data_access/dummy.go
+++ b/backend/pkg/api/data_access/dummy.go
@@ -637,9 +637,7 @@ func (d *DummyService) GetRocketPoolOverview(ctx context.Context) (*t.RocketPool
}
func (d *DummyService) GetApiWeights(ctx context.Context) ([]t.ApiWeightItem, error) {
- r := []t.ApiWeightItem{}
- err := commonFakeData(&r)
- return r, err
+ return getDummyData[[]t.ApiWeightItem]()
}
func (d *DummyService) GetHealthz(ctx context.Context, showAll bool) t.HealthzData {
@@ -654,3 +652,7 @@ func (d *DummyService) GetLatestBundleForNativeVersion(ctx context.Context, nati
func (d *DummyService) IncrementBundleDeliveryCount(ctx context.Context, bundleVerison uint64) error {
return nil
}
+
+func (d *DummyService) GetValidatorDashboardMobileWidget(ctx context.Context, dashboardId t.VDBIdPrimary) (*t.MobileWidgetData, error) {
+ return getDummyStruct[t.MobileWidgetData]()
+}
diff --git a/backend/pkg/api/data_access/vdb.go b/backend/pkg/api/data_access/vdb.go
index 1d38e5537..e7498702f 100644
--- a/backend/pkg/api/data_access/vdb.go
+++ b/backend/pkg/api/data_access/vdb.go
@@ -79,4 +79,6 @@ type ValidatorDashboardRepository interface {
GetValidatorDashboardTotalRocketPool(ctx context.Context, dashboardId t.VDBId, search string) (*t.VDBRocketPoolTableRow, error)
GetValidatorDashboardNodeRocketPool(ctx context.Context, dashboardId t.VDBId, node string) (*t.VDBNodeRocketPoolData, error)
GetValidatorDashboardRocketPoolMinipools(ctx context.Context, dashboardId t.VDBId, node, cursor string, colSort t.Sort[enums.VDBRocketPoolMinipoolsColumn], search string, limit uint64) ([]t.VDBRocketPoolMinipoolsTableRow, *t.Paging, error)
+
+ GetValidatorDashboardMobileWidget(ctx context.Context, dashboardId t.VDBIdPrimary) (*t.MobileWidgetData, error)
}
diff --git a/backend/pkg/api/data_access/vdb_management.go b/backend/pkg/api/data_access/vdb_management.go
index 3bd217507..d415f0a23 100644
--- a/backend/pkg/api/data_access/vdb_management.go
+++ b/backend/pkg/api/data_access/vdb_management.go
@@ -1213,3 +1213,9 @@ func (d *DataAccessService) GetValidatorDashboardPublicIdCount(ctx context.Conte
`, dashboardId)
return count, err
}
+
+func (d *DataAccessService) GetValidatorDashboardMobileWidget(ctx context.Context, dashboardId t.VDBIdPrimary) (*t.MobileWidgetData, error) {
+ // TODO @Data-Access: Implement this function
+ // feel free to move this func to other file if needed
+ return d.dummy.GetValidatorDashboardMobileWidget(ctx, dashboardId)
+}
diff --git a/backend/pkg/api/handlers/common.go b/backend/pkg/api/handlers/common.go
index ca585724b..a2c68cf24 100644
--- a/backend/pkg/api/handlers/common.go
+++ b/backend/pkg/api/handlers/common.go
@@ -78,6 +78,7 @@ const (
sortOrderAscending = "asc"
sortOrderDescending = "desc"
defaultSortOrder = sortOrderAscending
+ defaultDesc = defaultSortOrder == sortOrderDescending
ethereum = "ethereum"
gnosis = "gnosis"
allowEmpty = true
@@ -544,7 +545,7 @@ func checkEnumIsAllowed[T enums.EnumFactory[T]](v *validationError, enum T, allo
func (v *validationError) parseSortOrder(order string) bool {
switch order {
case "":
- return defaultSortOrder == sortOrderDescending
+ return defaultDesc
case sortOrderAscending:
return false
case sortOrderDescending:
@@ -558,19 +559,21 @@ func (v *validationError) parseSortOrder(order string) bool {
func checkSort[T enums.EnumFactory[T]](v *validationError, sortString string) *types.Sort[T] {
var c T
if sortString == "" {
- return &types.Sort[T]{Column: c, Desc: false}
+ return &types.Sort[T]{Column: c, Desc: defaultDesc}
}
sortSplit := strings.Split(sortString, ":")
if len(sortSplit) > 2 {
v.add("sort", fmt.Sprintf("given value '%s' for parameter 'sort' is not valid, expected format is '[:(asc|desc)]'", sortString))
return nil
}
+ var desc bool
if len(sortSplit) == 1 {
- sortSplit = append(sortSplit, ":asc")
+ desc = defaultDesc
+ } else {
+ desc = v.parseSortOrder(sortSplit[1])
}
sortCol := checkEnum[T](v, sortSplit[0], "sort")
- order := v.parseSortOrder(sortSplit[1])
- return &types.Sort[T]{Column: sortCol, Desc: order}
+ return &types.Sort[T]{Column: sortCol, Desc: desc}
}
func (v *validationError) checkProtocolModes(protocolModes string) types.VDBProtocolModes {
diff --git a/backend/pkg/api/handlers/internal.go b/backend/pkg/api/handlers/internal.go
index 0a0f5c006..78d998856 100644
--- a/backend/pkg/api/handlers/internal.go
+++ b/backend/pkg/api/handlers/internal.go
@@ -475,6 +475,39 @@ func (h *HandlerService) InternalGetValidatorDashboardRocketPoolMinipools(w http
h.PublicGetValidatorDashboardRocketPoolMinipools(w, r)
}
+// even though this endpoint is internal only, it should still not be broken since it is used by the mobile app
+func (h *HandlerService) InternalGetValidatorDashboardMobileWidget(w http.ResponseWriter, r *http.Request) {
+ var v validationError
+ dashboardId := v.checkPrimaryDashboardId(mux.Vars(r)["dashboard_id"])
+ if v.hasErrors() {
+ handleErr(w, r, v)
+ return
+ }
+ userId, err := GetUserIdByContext(r)
+ if err != nil {
+ handleErr(w, r, err)
+ return
+ }
+ userInfo, err := h.dai.GetUserInfo(r.Context(), userId)
+ if err != nil {
+ handleErr(w, r, err)
+ return
+ }
+ if userInfo.UserGroup != types.UserGroupAdmin && !userInfo.PremiumPerks.MobileAppWidget {
+ returnForbidden(w, r, errors.New("user does not have access to mobile app widget"))
+ return
+ }
+ data, err := h.dai.GetValidatorDashboardMobileWidget(r.Context(), dashboardId)
+ if err != nil {
+ handleErr(w, r, err)
+ return
+ }
+ response := types.InternalGetValidatorDashboardMobileWidgetResponse{
+ Data: *data,
+ }
+ returnOk(w, r, response)
+}
+
// --------------------------------------
// Mobile
diff --git a/backend/pkg/api/handlers/public.go b/backend/pkg/api/handlers/public.go
index b3ffd15dd..e0e560b8c 100644
--- a/backend/pkg/api/handlers/public.go
+++ b/backend/pkg/api/handlers/public.go
@@ -61,6 +61,14 @@ func (h *HandlerService) PublicGetHealthzLoadbalancer(w http.ResponseWriter, r *
returnOk(w, r, nil)
}
+// PublicGetUserDashboards godoc
+//
+// @Description Get all dashboards of the authenticated user.
+// @Security ApiKeyInHeader || ApiKeyInQuery
+// @Tags Dashboards
+// @Produce json
+// @Success 200 {object} types.ApiDataResponse[types.UserDashboardsData]
+// @Router /users/me/dashboards [get]
func (h *HandlerService) PublicGetUserDashboards(w http.ResponseWriter, r *http.Request) {
userId, err := GetUserIdByContext(r)
if err != nil {
@@ -859,7 +867,7 @@ func (h *HandlerService) PublicDeleteValidatorDashboardPublicId(w http.ResponseW
// @Param request body handlers.PublicPutValidatorDashboardArchiving.request true "request"
// @Success 200 {object} types.ApiDataResponse[types.VDBPostArchivingReturnData]
// @Failure 400 {object} types.ApiErrorResponse
-// @Conflict 409 {object} types.ApiErrorResponse "Conflict. The request could not be performed by the server because the authenticated user has already reached their subscription limit."
+// @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."
// @Router /validator-dashboards/{dashboard_id}/archiving [put]
func (h *HandlerService) PublicPutValidatorDashboardArchiving(w http.ResponseWriter, r *http.Request) {
var v validationError
diff --git a/backend/pkg/api/router.go b/backend/pkg/api/router.go
index 3238257bd..16ab4c0b2 100644
--- a/backend/pkg/api/router.go
+++ b/backend/pkg/api/router.go
@@ -304,6 +304,7 @@ func addValidatorDashboardRoutes(hs *handlers.HandlerService, publicRouter, inte
{http.MethodGet, "/{dashboard_id}/total-rocket-pool", hs.PublicGetValidatorDashboardTotalRocketPool, hs.InternalGetValidatorDashboardTotalRocketPool},
{http.MethodGet, "/{dashboard_id}/rocket-pool/{node_address}", hs.PublicGetValidatorDashboardNodeRocketPool, hs.InternalGetValidatorDashboardNodeRocketPool},
{http.MethodGet, "/{dashboard_id}/rocket-pool/{node_address}/minipools", hs.PublicGetValidatorDashboardRocketPoolMinipools, hs.InternalGetValidatorDashboardRocketPoolMinipools},
+ {http.MethodGet, "/{dashboard_id}/mobile-widget", nil, hs.InternalGetValidatorDashboardMobileWidget},
}
addEndpointsToRouters(endpoints, publicDashboardRouter, internalDashboardRouter)
}
diff --git a/backend/pkg/api/types/common.go b/backend/pkg/api/types/common.go
index 2deebb1b9..6f2856768 100644
--- a/backend/pkg/api/types/common.go
+++ b/backend/pkg/api/types/common.go
@@ -145,3 +145,11 @@ type IndexBlocks struct {
Index uint64 `json:"index"`
Blocks []uint64 `json:"blocks"`
}
+
+type ValidatorStateCounts struct {
+ Online uint64 `json:"online"`
+ Offline uint64 `json:"offline"`
+ Pending uint64 `json:"pending"`
+ Exited uint64 `json:"exited"`
+ Slashed uint64 `json:"slashed"`
+}
diff --git a/backend/pkg/api/types/dashboard.go b/backend/pkg/api/types/dashboard.go
index d3e334722..340a21b01 100644
--- a/backend/pkg/api/types/dashboard.go
+++ b/backend/pkg/api/types/dashboard.go
@@ -5,14 +5,14 @@ type AccountDashboard struct {
Name string `json:"name"`
}
type ValidatorDashboard struct {
- Id uint64 `json:"id"`
- Name string `json:"name"`
- Network uint64 `json:"network"`
- PublicIds []VDBPublicId `json:"public_ids,omitempty"`
- IsArchived bool `json:"is_archived"`
- ArchivedReason string `json:"archived_reason,omitempty" tstype:"'user' | 'dashboard_limit' | 'validator_limit' | 'group_limit'"`
- ValidatorCount uint64 `json:"validator_count"`
- GroupCount uint64 `json:"group_count"`
+ Id uint64 `json:"id" extensions:"x-order=1"`
+ Name string `json:"name" extensions:"x-order=2"`
+ Network uint64 `json:"network" extensions:"x-order=3"`
+ PublicIds []VDBPublicId `json:"public_ids,omitempty" extensions:"x-order=4"`
+ IsArchived bool `json:"is_archived" extensions:"x-order=5"`
+ ArchivedReason string `json:"archived_reason,omitempty" tstype:"'user' | 'dashboard_limit' | 'validator_limit' | 'group_limit'" extensions:"x-order=6"`
+ ValidatorCount uint64 `json:"validator_count" extensions:"x-order=7"`
+ GroupCount uint64 `json:"group_count" extensions:"x-order=8"`
}
type UserDashboardsData struct {
diff --git a/backend/pkg/api/types/mobile.go b/backend/pkg/api/types/mobile.go
index 7f84a999d..62c323b0d 100644
--- a/backend/pkg/api/types/mobile.go
+++ b/backend/pkg/api/types/mobile.go
@@ -1,8 +1,23 @@
package types
+import "github.com/shopspring/decimal"
+
type MobileBundleData struct {
BundleUrl string `json:"bundle_url,omitempty"`
HasNativeUpdateAvailable bool `json:"has_native_update_available"`
}
type GetMobileLatestBundleResponse ApiDataResponse[MobileBundleData]
+
+type MobileWidgetData struct {
+ ValidatorStateCounts ValidatorStateCounts `json:"validator_state_counts"`
+ Last24hIncome decimal.Decimal `json:"last_24h_income" faker:"eth"`
+ Last7dIncome decimal.Decimal `json:"last_7d_income" faker:"eth"`
+ Last30dApr float64 `json:"last_30d_apr"`
+ Last30dEfficiency decimal.Decimal `json:"last_30d_efficiency" faker:"eth"`
+ NetworkEfficiency float64 `json:"network_efficiency"`
+ RplPrice decimal.Decimal `json:"rpl_price" faker:"eth"`
+ RplApr float64 `json:"rpl_apr"`
+}
+
+type InternalGetValidatorDashboardMobileWidgetResponse ApiDataResponse[MobileWidgetData]
diff --git a/backend/pkg/api/types/validator_dashboard.go b/backend/pkg/api/types/validator_dashboard.go
index 380d035d4..f5df0b039 100644
--- a/backend/pkg/api/types/validator_dashboard.go
+++ b/backend/pkg/api/types/validator_dashboard.go
@@ -6,13 +6,6 @@ import (
// ------------------------------------------------------------
// Overview
-type VDBOverviewValidators struct {
- Online uint64 `json:"online"`
- Offline uint64 `json:"offline"`
- Pending uint64 `json:"pending"`
- Exited uint64 `json:"exited"`
- Slashed uint64 `json:"slashed"`
-}
type VDBOverviewGroup struct {
Id uint64 `json:"id"`
@@ -30,7 +23,7 @@ type VDBOverviewData struct {
Name string `json:"name,omitempty" extensions:"x-order=1"`
Network uint64 `json:"network"`
Groups []VDBOverviewGroup `json:"groups"`
- Validators VDBOverviewValidators `json:"validators"`
+ Validators ValidatorStateCounts `json:"validators"`
Efficiency PeriodicValues[float64] `json:"efficiency"`
Rewards PeriodicValues[ClElValue[decimal.Decimal]] `json:"rewards"`
Apr PeriodicValues[ClElValue[float64]] `json:"apr"`
diff --git a/frontend/types/api/common.ts b/frontend/types/api/common.ts
index 3c8c9506a..21cd77662 100644
--- a/frontend/types/api/common.ts
+++ b/frontend/types/api/common.ts
@@ -117,3 +117,10 @@ export interface IndexBlocks {
index: number /* uint64 */;
blocks: number /* uint64 */[];
}
+export interface ValidatorStateCounts {
+ online: number /* uint64 */;
+ offline: number /* uint64 */;
+ pending: number /* uint64 */;
+ exited: number /* uint64 */;
+ slashed: number /* uint64 */;
+}
diff --git a/frontend/types/api/mobile.ts b/frontend/types/api/mobile.ts
index d6b234a18..335bd2aef 100644
--- a/frontend/types/api/mobile.ts
+++ b/frontend/types/api/mobile.ts
@@ -1,6 +1,6 @@
// Code generated by tygo. DO NOT EDIT.
/* eslint-disable */
-import type { ApiDataResponse } from './common'
+import type { ApiDataResponse, ValidatorStateCounts } from './common'
//////////
// source: mobile.go
@@ -10,3 +10,14 @@ export interface MobileBundleData {
has_native_update_available: boolean;
}
export type GetMobileLatestBundleResponse = ApiDataResponse;
+export interface MobileWidgetData {
+ validator_state_counts: ValidatorStateCounts;
+ last_24h_income: string /* decimal.Decimal */;
+ last_7d_income: string /* decimal.Decimal */;
+ last_30d_apr: number /* float64 */;
+ last_30d_efficiency: string /* decimal.Decimal */;
+ network_efficiency: number /* float64 */;
+ rpl_price: string /* decimal.Decimal */;
+ rpl_apr: number /* float64 */;
+}
+export type InternalGetValidatorDashboardMobileWidgetResponse = ApiDataResponse;
diff --git a/frontend/types/api/validator_dashboard.ts b/frontend/types/api/validator_dashboard.ts
index 03f07f548..58bc40cce 100644
--- a/frontend/types/api/validator_dashboard.ts
+++ b/frontend/types/api/validator_dashboard.ts
@@ -1,21 +1,10 @@
// Code generated by tygo. DO NOT EDIT.
/* eslint-disable */
-import type { PeriodicValues, ClElValue, ChartHistorySeconds, ApiDataResponse, StatusCount, ApiPagingResponse, Luck, ChartData, ValidatorHistoryDuties, Address, PubKey, Hash, PercentageDetails } from './common'
+import type { ValidatorStateCounts, PeriodicValues, ClElValue, ChartHistorySeconds, ApiDataResponse, StatusCount, ApiPagingResponse, Luck, ChartData, ValidatorHistoryDuties, Address, PubKey, Hash, PercentageDetails } from './common'
//////////
// source: validator_dashboard.go
-/**
- * ------------------------------------------------------------
- * Overview
- */
-export interface VDBOverviewValidators {
- online: number /* uint64 */;
- offline: number /* uint64 */;
- pending: number /* uint64 */;
- exited: number /* uint64 */;
- slashed: number /* uint64 */;
-}
export interface VDBOverviewGroup {
id: number /* uint64 */;
name: string;
@@ -30,7 +19,7 @@ export interface VDBOverviewData {
name?: string;
network: number /* uint64 */;
groups: VDBOverviewGroup[];
- validators: VDBOverviewValidators;
+ validators: ValidatorStateCounts;
efficiency: PeriodicValues;
rewards: PeriodicValues>;
apr: PeriodicValues>;
From 62d7b22b3c121f550745d2eb2e3ab83ee8d21bfd Mon Sep 17 00:00:00 2001
From: Lucca <109136188+LuccaBitfly@users.noreply.github.com>
Date: Thu, 19 Sep 2024 10:48:58 +0200
Subject: [PATCH 138/187] (BEDS-501) add enabled flag to all thresholds (#870)
---
backend/pkg/api/handlers/common.go | 7 ++---
backend/pkg/api/types/notifications.go | 31 ++++++++++++-------
.../playground/PlaygroundDialog.vue | 1 +
frontend/types/api/notifications.ts | 29 +++++++++++------
frontend/utils/mock.ts | 5 +++
5 files changed, 47 insertions(+), 26 deletions(-)
diff --git a/backend/pkg/api/handlers/common.go b/backend/pkg/api/handlers/common.go
index a2c68cf24..ca7c41bb1 100644
--- a/backend/pkg/api/handlers/common.go
+++ b/backend/pkg/api/handlers/common.go
@@ -2,6 +2,7 @@ package handlers
import (
"bytes"
+ "cmp"
"context"
"encoding/base64"
"encoding/json"
@@ -477,11 +478,7 @@ func (v *validationError) checkValidatorDashboardPublicId(publicId string) types
return types.VDBIdPublic(v.checkRegex(reValidatorDashboardPublicId, publicId, "public_dashboard_id"))
}
-type number interface {
- uint64 | int64 | float64
-}
-
-func checkMinMax[T number](v *validationError, param T, min T, max T, paramName string) T {
+func checkMinMax[T cmp.Ordered](v *validationError, param T, min T, max T, paramName string) T {
if param < min {
v.add(paramName, fmt.Sprintf("given value '%v' is too small, minimum value is %v", param, min))
}
diff --git a/backend/pkg/api/types/notifications.go b/backend/pkg/api/types/notifications.go
index f183988b1..27bf585be 100644
--- a/backend/pkg/api/types/notifications.go
+++ b/backend/pkg/api/types/notifications.go
@@ -141,9 +141,12 @@ type InternalGetUserNotificationNetworksResponse ApiPagingResponse[NotificationN
// ------------------------------------------------------------
// Notification Settings
type NotificationSettingsNetwork struct {
- GasAboveThreshold decimal.Decimal `json:"gas_above_threshold" faker:"eth"` // 0 is disabled
- GasBelowThreshold decimal.Decimal `json:"gas_below_threshold" faker:"eth"` // 0 is disabled
- ParticipationRateThreshold float64 `json:"participation_rate_threshold" faker:"boundary_start=0, boundary_end=1"` // 0 is disabled
+ IsGasAboveSubscribed bool `json:"is_gas_above_subscribed"`
+ GasAboveThreshold decimal.Decimal `json:"gas_above_threshold" faker:"eth"`
+ IsGasBelowSubscribed bool `json:"is_gas_below_subscribed"`
+ GasBelowThreshold decimal.Decimal `json:"gas_below_threshold" faker:"eth"`
+ IsParticipationRateSubscribed bool `json:"is_participation_rate_subscribed"`
+ ParticipationRateThreshold float64 `json:"participation_rate_threshold" faker:"boundary_start=0, boundary_end=1"`
}
type NotificationNetwork struct {
ChainId uint64 `json:"chain_id"`
@@ -164,15 +167,20 @@ type NotificationSettingsGeneral struct {
IsEmailNotificationsEnabled bool `json:"is_email_notifications_enabled"`
IsPushNotificationsEnabled bool `json:"is_push_notifications_enabled"`
- IsMachineOfflineSubscribed bool `json:"is_machine_offline_subscribed"`
- MachineStorageUsageThreshold float64 `json:"machine_storage_usage_threshold" faker:"boundary_start=0, boundary_end=1"` // 0 means disabled
- MachineCpuUsageThreshold float64 `json:"machine_cpu_usage_threshold" faker:"boundary_start=0, boundary_end=1"` // 0 means disabled
- MachineMemoryUsageThreshold float64 `json:"machine_memory_usage_threshold" faker:"boundary_start=0, boundary_end=1"` // 0 means disabled
+ IsMachineOfflineSubscribed bool `json:"is_machine_offline_subscribed"`
+ IsMachineStorageUsageSubscribed bool `json:"is_machine_storage_usage_subscribed"`
+ MachineStorageUsageThreshold float64 `json:"machine_storage_usage_threshold" faker:"boundary_start=0, boundary_end=1"`
+ IsMachineCpuUsageSubscribed bool `json:"is_machine_cpu_usage_subscribed"`
+ MachineCpuUsageThreshold float64 `json:"machine_cpu_usage_threshold" faker:"boundary_start=0, boundary_end=1"`
+ 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"`
- RocketPoolMaxCollateralThreshold float64 `json:"rocket_pool_max_collateral_threshold" faker:"boundary_start=0, boundary_end=1"` // 0 means disabled
- RocketPoolMinCollateralThreshold float64 `json:"rocket_pool_min_collateral_threshold" faker:"boundary_start=0, boundary_end=1"` // 0 means disabled
+ 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 {
@@ -188,7 +196,8 @@ type NotificationSettingsValidatorDashboard struct {
IsRealTimeModeEnabled bool `json:"is_real_time_mode_enabled"`
IsValidatorOfflineSubscribed bool `json:"is_validator_offline_subscribed"`
- GroupOfflineThreshold float64 `json:"group_offline_threshold" faker:"boundary_start=0, boundary_end=1"` // 0 is disabled
+ IsGroupOfflineSubscribed bool `json:"is_group_offline_subscribed"`
+ GroupOfflineThreshold float64 `json:"group_offline_threshold" faker:"boundary_start=0, boundary_end=1"`
IsAttestationsMissedSubscribed bool `json:"is_attestations_missed_subscribed"`
IsBlockProposalSubscribed bool `json:"is_block_proposal_subscribed"`
IsUpcomingBlockProposalSubscribed bool `json:"is_upcoming_block_proposal_subscribed"`
@@ -208,7 +217,7 @@ type NotificationSettingsAccountDashboard struct {
IsIncomingTransactionsSubscribed bool `json:"is_incoming_transactions_subscribed"`
IsOutgoingTransactionsSubscribed bool `json:"is_outgoing_transactions_subscribed"`
IsERC20TokenTransfersSubscribed bool `json:"is_erc20_token_transfers_subscribed"`
- ERC20TokenTransfersValueThreshold float64 `json:"erc20_token_transfers_value_threshold" faker:"boundary_start=0, boundary_end=1000000"` // 0 does not disable, is_erc20_token_transfers_subscribed determines if it's enabled
+ ERC20TokenTransfersValueThreshold float64 `json:"erc20_token_transfers_value_threshold" faker:"boundary_start=0, boundary_end=1000000"`
IsERC721TokenTransfersSubscribed bool `json:"is_erc721_token_transfers_subscribed"`
IsERC1155TokenTransfersSubscribed bool `json:"is_erc1155_token_transfers_subscribed"`
}
diff --git a/frontend/components/playground/PlaygroundDialog.vue b/frontend/components/playground/PlaygroundDialog.vue
index 72c8d4082..b9d2c3c35 100644
--- a/frontend/components/playground/PlaygroundDialog.vue
+++ b/frontend/components/playground/PlaygroundDialog.vue
@@ -34,6 +34,7 @@ const validatorSub: NotificationSettingsValidatorDashboard = {
group_offline_threshold: 0, // means "deactivated/unchecked"
is_attestations_missed_subscribed: true,
is_block_proposal_subscribed: true,
+ is_group_offline_subscribed: true,
is_real_time_mode_enabled: false,
is_slashed_subscribed: false,
is_sync_subscribed: true,
diff --git a/frontend/types/api/notifications.ts b/frontend/types/api/notifications.ts
index e22be1d6a..0d9f53898 100644
--- a/frontend/types/api/notifications.ts
+++ b/frontend/types/api/notifications.ts
@@ -137,9 +137,12 @@ export type InternalGetUserNotificationNetworksResponse = ApiPagingResponse;
export interface NotificationSettings {
@@ -178,7 +186,8 @@ export interface NotificationSettingsValidatorDashboard {
is_webhook_discord_enabled: boolean;
is_real_time_mode_enabled: boolean;
is_validator_offline_subscribed: boolean;
- group_offline_threshold: number /* float64 */; // 0 is disabled
+ is_group_offline_subscribed: boolean;
+ group_offline_threshold: number /* float64 */;
is_attestations_missed_subscribed: boolean;
is_block_proposal_subscribed: boolean;
is_upcoming_block_proposal_subscribed: boolean;
@@ -195,7 +204,7 @@ export interface NotificationSettingsAccountDashboard {
is_incoming_transactions_subscribed: boolean;
is_outgoing_transactions_subscribed: boolean;
is_erc20_token_transfers_subscribed: boolean;
- erc20_token_transfers_value_threshold: number /* float64 */; // 0 does not disable, is_erc20_token_transfers_subscribed determines if it's enabled
+ erc20_token_transfers_value_threshold: number /* float64 */;
is_erc721_token_transfers_subscribed: boolean;
is_erc1155_token_transfers_subscribed: boolean;
}
diff --git a/frontend/utils/mock.ts b/frontend/utils/mock.ts
index e1c25fef2..f942d8fbb 100644
--- a/frontend/utils/mock.ts
+++ b/frontend/utils/mock.ts
@@ -446,8 +446,13 @@ export function mockManageNotificationsGeneral(): InternalGetUserNotificationSet
general_settings: {
do_not_disturb_timestamp: 9000,
is_email_notifications_enabled: false,
+ is_machine_cpu_usage_subscribed: true,
+ is_machine_memory_usage_subscribed: true,
is_machine_offline_subscribed: true,
+ is_machine_storage_usage_subscribed: true,
is_push_notifications_enabled: true,
+ is_rocket_pool_max_collateral_subscribed: true,
+ is_rocket_pool_min_collateral_subscribed: true,
is_rocket_pool_new_reward_round_subscribed: true,
machine_cpu_usage_threshold: 40,
machine_memory_usage_threshold: 50,
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 139/187] (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 140/187] (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 141/187] (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 142/187] (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 143/187] (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 144/187] 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 171/187] (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 172/187] 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 })
+
}}