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] (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>;