diff --git a/backend/pkg/api/data_access/dummy.go b/backend/pkg/api/data_access/dummy.go index 7bee9a38c..3e42c6384 100644 --- a/backend/pkg/api/data_access/dummy.go +++ b/backend/pkg/api/data_access/dummy.go @@ -448,7 +448,7 @@ func (d *DummyService) GetValidatorDashboardPublicIdCount(ctx context.Context, d func (d *DummyService) GetNotificationOverview(ctx context.Context, userId uint64) (*t.NotificationOverviewData, error) { return getDummyStruct[t.NotificationOverviewData]() } -func (d *DummyService) GetDashboardNotifications(ctx context.Context, userId uint64, chainId uint64, cursor string, colSort t.Sort[enums.NotificationDashboardsColumn], search string, limit uint64) ([]t.NotificationDashboardsTableRow, *t.Paging, error) { +func (d *DummyService) GetDashboardNotifications(ctx context.Context, userId uint64, chainIds []uint64, cursor string, colSort t.Sort[enums.NotificationDashboardsColumn], search string, limit uint64) ([]t.NotificationDashboardsTableRow, *t.Paging, error) { return getDummyWithPaging[t.NotificationDashboardsTableRow]() } diff --git a/backend/pkg/api/data_access/notifications.go b/backend/pkg/api/data_access/notifications.go index 5389847db..7e5ae9d50 100644 --- a/backend/pkg/api/data_access/notifications.go +++ b/backend/pkg/api/data_access/notifications.go @@ -10,7 +10,7 @@ import ( type NotificationsRepository interface { GetNotificationOverview(ctx context.Context, userId uint64) (*t.NotificationOverviewData, error) - GetDashboardNotifications(ctx context.Context, userId uint64, chainId uint64, cursor string, colSort t.Sort[enums.NotificationDashboardsColumn], search string, limit uint64) ([]t.NotificationDashboardsTableRow, *t.Paging, error) + GetDashboardNotifications(ctx context.Context, userId uint64, chainIds []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, dashboardId t.VDBIdPrimary, groupId uint64, epoch uint64) (*t.NotificationValidatorDashboardDetail, error) GetAccountDashboardNotificationDetails(ctx context.Context, dashboardId uint64, groupId uint64, epoch uint64) (*t.NotificationAccountDashboardDetail, error) @@ -33,8 +33,8 @@ type NotificationsRepository interface { func (d *DataAccessService) GetNotificationOverview(ctx context.Context, userId uint64) (*t.NotificationOverviewData, error) { return d.dummy.GetNotificationOverview(ctx, userId) } -func (d *DataAccessService) GetDashboardNotifications(ctx context.Context, userId uint64, chainId uint64, cursor string, colSort t.Sort[enums.NotificationDashboardsColumn], search string, limit uint64) ([]t.NotificationDashboardsTableRow, *t.Paging, error) { - return d.dummy.GetDashboardNotifications(ctx, userId, chainId, cursor, colSort, search, limit) +func (d *DataAccessService) GetDashboardNotifications(ctx context.Context, userId uint64, chainIds []uint64, cursor string, colSort t.Sort[enums.NotificationDashboardsColumn], search string, limit uint64) ([]t.NotificationDashboardsTableRow, *t.Paging, error) { + return d.dummy.GetDashboardNotifications(ctx, userId, chainIds, cursor, colSort, search, limit) } func (d *DataAccessService) GetValidatorDashboardNotificationDetails(ctx context.Context, dashboardId t.VDBIdPrimary, groupId uint64, epoch uint64) (*t.NotificationValidatorDashboardDetail, error) { diff --git a/backend/pkg/api/enums/notifications_enums.go b/backend/pkg/api/enums/notifications_enums.go index 4367c44ee..554ea2afe 100644 --- a/backend/pkg/api/enums/notifications_enums.go +++ b/backend/pkg/api/enums/notifications_enums.go @@ -10,7 +10,7 @@ var _ EnumFactory[NotificationDashboardsColumn] = NotificationDashboardsColumn(0 const ( NotificationDashboardChainId NotificationDashboardsColumn = iota NotificationDashboardTimestamp - NotificationDashboardDashboardId // sort by name + NotificationDashboardDashboardName // sort by name ) func (c NotificationDashboardsColumn) Int() int { @@ -23,8 +23,8 @@ func (NotificationDashboardsColumn) NewFromString(s string) NotificationDashboar return NotificationDashboardChainId case "timestamp": return NotificationDashboardTimestamp - case "dashboard_id": - return NotificationDashboardDashboardId + case "dashboard_name", "dashboard_id": // accepting id for frontend + return NotificationDashboardDashboardName default: return NotificationDashboardsColumn(-1) } @@ -37,7 +37,7 @@ var NotificationsDashboardsColumns = struct { }{ NotificationDashboardChainId, NotificationDashboardTimestamp, - NotificationDashboardDashboardId, + NotificationDashboardDashboardName, } // ------------------------------------------------------------ @@ -203,7 +203,7 @@ type NotificationSettingsDashboardColumn int var _ EnumFactory[NotificationSettingsDashboardColumn] = NotificationSettingsDashboardColumn(0) const ( - NotificationSettingsDashboardDashboardId NotificationSettingsDashboardColumn = iota + NotificationSettingsDashboardDashboardName NotificationSettingsDashboardColumn = iota NotificationSettingsDashboardGroupName ) @@ -213,8 +213,8 @@ func (c NotificationSettingsDashboardColumn) Int() int { func (NotificationSettingsDashboardColumn) NewFromString(s string) NotificationSettingsDashboardColumn { switch s { - case "dashboard_id": - return NotificationSettingsDashboardDashboardId + case "dashboard_name", "dashboard_id": + return NotificationSettingsDashboardDashboardName case "group_name": return NotificationSettingsDashboardGroupName default: @@ -226,6 +226,6 @@ var NotificationSettingsDashboardColumns = struct { DashboardId NotificationSettingsDashboardColumn GroupName NotificationSettingsDashboardColumn }{ - NotificationSettingsDashboardDashboardId, + NotificationSettingsDashboardDashboardName, NotificationSettingsDashboardGroupName, } diff --git a/backend/pkg/api/handlers/common.go b/backend/pkg/api/handlers/common.go index ca7c41bb1..a667b029b 100644 --- a/backend/pkg/api/handlers/common.go +++ b/backend/pkg/api/handlers/common.go @@ -658,6 +658,14 @@ func (v *validationError) checkNetworkParameter(param string) uint64 { return v.checkNetwork(intOrString{strValue: ¶m}) } +func (v *validationError) checkNetworksParameter(param string) []uint64 { + var chainIds []uint64 + for _, network := range splitParameters(param, ',') { + chainIds = append(chainIds, v.checkNetworkParameter(network)) + } + return chainIds +} + // isValidNetwork checks if the given network is a valid network. // It returns the chain id of the network and true if it is valid, otherwise 0 and false. func isValidNetwork(network intOrString) (uint64, bool) { diff --git a/backend/pkg/api/handlers/public.go b/backend/pkg/api/handlers/public.go index e0e560b8c..a2a92171a 100644 --- a/backend/pkg/api/handlers/public.go +++ b/backend/pkg/api/handlers/public.go @@ -25,7 +25,6 @@ import ( // @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 @@ -1901,7 +1900,7 @@ func (h *HandlerService) PublicGetUserNotifications(w http.ResponseWriter, r *ht // @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 networks 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) @@ -1919,12 +1918,12 @@ func (h *HandlerService) PublicGetUserNotificationDashboards(w http.ResponseWrit q := r.URL.Query() pagingParams := v.checkPagingParams(q) sort := checkSort[enums.NotificationDashboardsColumn](&v, q.Get("sort")) - chainId := v.checkNetworkParameter(q.Get("network")) + chainIds := v.checkNetworksParameter(q.Get("networks")) 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) + data, paging, err := h.dai.GetDashboardNotifications(r.Context(), userId, chainIds, pagingParams.cursor, *sort, pagingParams.search, pagingParams.limit) if err != nil { handleErr(w, r, err) return @@ -2467,14 +2466,7 @@ func (h *HandlerService) PublicPutUserNotificationSettingsAccountDashboard(w htt 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++ - } + chainIds := v.checkNetworkSlice(req.SubscribedChainIds) checkMinMax(&v, req.ERC20TokenTransfersValueThreshold, 0, math.MaxFloat64, "group_offline_threshold") vars := mux.Vars(r) dashboardId := v.checkPrimaryDashboardId(vars["dashboard_id"]) diff --git a/backend/pkg/api/handlers/search_handlers.go b/backend/pkg/api/handlers/search_handlers.go index 6793e4ef0..fa332188a 100644 --- a/backend/pkg/api/handlers/search_handlers.go +++ b/backend/pkg/api/handlers/search_handlers.go @@ -5,8 +5,10 @@ import ( "encoding/hex" "errors" "fmt" + "maps" "net/http" "regexp" + "slices" "strconv" "strings" @@ -68,12 +70,12 @@ func (h *HandlerService) InternalPostSearch(w http.ResponseWriter, r *http.Reque searchResultChan := make(chan types.SearchResult) // iterate over all combinations of search types and networks - for searchType := range searchTypeSet { + for _, searchType := range searchTypeSet { // check if input matches the regex for the search type if !searchTypeToRegex[searchType].MatchString(req.Input) { continue } - for chainId := range chainIdSet { + for _, chainId := range chainIdSet { chainId := chainId searchType := searchType g.Go(func() error { @@ -326,14 +328,14 @@ func (h *HandlerService) handleSearchValidatorsByGraffiti(ctx context.Context, i // Input Validation // if the passed slice is empty, return a set with all chain IDs; otherwise check if the passed networks are valid -func (v *validationError) checkNetworkSlice(networks []intOrString) map[uint64]struct{} { +func (v *validationError) checkNetworkSlice(networks []intOrString) []uint64 { networkSet := map[uint64]struct{}{} // if the list is empty, query all networks if len(networks) == 0 { for _, n := range allNetworks { networkSet[n.ChainId] = struct{}{} } - return networkSet + return slices.Collect(maps.Keys(networkSet)) } // list not empty, check if networks are valid for _, network := range networks { @@ -344,18 +346,18 @@ func (v *validationError) checkNetworkSlice(networks []intOrString) map[uint64]s } networkSet[chainId] = struct{}{} } - return networkSet + return slices.Collect(maps.Keys(networkSet)) } // if the passed slice is empty, return a set with all search types; otherwise check if the passed types are valid -func (v *validationError) checkSearchTypes(types []searchTypeKey) map[searchTypeKey]struct{} { +func (v *validationError) checkSearchTypes(types []searchTypeKey) []searchTypeKey { typeSet := map[searchTypeKey]struct{}{} // if the list is empty, query all types if len(types) == 0 { for t := range searchTypeToRegex { typeSet[t] = struct{}{} } - return typeSet + return slices.Collect(maps.Keys(typeSet)) } // list not empty, check if types are valid for _, t := range types { @@ -365,5 +367,5 @@ func (v *validationError) checkSearchTypes(types []searchTypeKey) map[searchType } typeSet[t] = struct{}{} } - return typeSet + return slices.Collect(maps.Keys(typeSet)) }