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:" @@ -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) }