From 993421ae8a0b5594923f13ef64e36ac211239e79 Mon Sep 17 00:00:00 2001 From: chris-4chain <152964795+chris-4chain@users.noreply.github.com> Date: Fri, 3 Jan 2025 11:46:51 +0100 Subject: [PATCH] feat(SPV-1345): get webhooks (#835) --- actions/admin/routes.go | 3 +- actions/admin/webhooks.go | 25 ++++++++ actions/admin/webhooks_old.go | 4 +- actions/admin/webhooks_test.go | 72 ++++++++++++++++++++++ docs/docs.go | 29 +++++++++ docs/swagger.json | 29 +++++++++ docs/swagger.yaml | 18 ++++++ engine/client.go | 8 +-- engine/datastore/sql.go | 8 +++ engine/testabilities/fixture_configopts.go | 6 ++ 10 files changed, 195 insertions(+), 7 deletions(-) create mode 100644 actions/admin/webhooks_test.go diff --git a/actions/admin/routes.go b/actions/admin/routes.go index e4393d822..d18013458 100644 --- a/actions/admin/routes.go +++ b/actions/admin/routes.go @@ -36,7 +36,7 @@ func RegisterRoutes(handlersManager *handlers.Manager) { adminGroupOld.POST("/xpubs/count", handlers.AsAdmin(xpubsCount)) adminGroupOld.POST("/webhooks/subscriptions", handlers.AsAdmin(subscribeWebhookOld)) adminGroupOld.DELETE("/webhooks/subscriptions", handlers.AsAdmin(unsubscribeWebhookOld)) - adminGroupOld.GET("/webhooks/subscriptions", handlers.AsAdmin(getAllWebhooks)) + adminGroupOld.GET("/webhooks/subscriptions", handlers.AsAdmin(getAllWebhooksOld)) adminGroupOld.GET("/transactions/:id", handlers.AsAdmin(getTxAdminByIDOld)) adminGroupOld.GET("/transactions", handlers.AsAdmin(getTransactionsOld)) @@ -71,6 +71,7 @@ func RegisterRoutes(handlersManager *handlers.Manager) { adminGroup.GET("/utxos", handlers.AsAdmin(utxosSearch)) // webhooks + adminGroup.GET("/webhooks/subscriptions", handlers.AsAdmin(getAllWebhooks)) adminGroup.POST("/webhooks/subscriptions", handlers.AsAdmin(subscribeWebhook)) adminGroup.DELETE("/webhooks/subscriptions", handlers.AsAdmin(unsubscribeWebhook)) diff --git a/actions/admin/webhooks.go b/actions/admin/webhooks.go index f09321279..0fada6406 100644 --- a/actions/admin/webhooks.go +++ b/actions/admin/webhooks.go @@ -4,6 +4,7 @@ import ( "net/http" "github.com/bitcoin-sv/spv-wallet/engine/spverrors" + "github.com/bitcoin-sv/spv-wallet/mappings" "github.com/bitcoin-sv/spv-wallet/models" "github.com/bitcoin-sv/spv-wallet/server/reqctx" "github.com/gin-gonic/gin" @@ -61,3 +62,27 @@ func unsubscribeWebhook(c *gin.Context, _ *reqctx.AdminContext) { c.Status(http.StatusOK) } + +// getAllWebhooks will return all the stored webhooks +// @Summary Get All Webhooks +// @Description Get All Webhooks currently subscribed to +// @Tags Admin +// @Produce json +// @Success 200 {object} []models.Webhook "List of webhooks" +// @Failure 500 "Internal server error - Error while getting all webhooks" +// @Router /api/v1/admin/webhooks/subscriptions [get] +// @Security x-auth-xpub +func getAllWebhooks(c *gin.Context, _ *reqctx.AdminContext) { + wh, err := reqctx.Engine(c).GetWebhooks(c.Request.Context()) + if err != nil { + spverrors.ErrorResponse(c, err, reqctx.Logger(c)) + return + } + + webhookDTOs := make([]*models.Webhook, len(wh)) + for i, w := range wh { + webhookDTOs[i] = mappings.MapToWebhookContract(w) + } + + c.JSON(http.StatusOK, webhookDTOs) +} diff --git a/actions/admin/webhooks_old.go b/actions/admin/webhooks_old.go index 0080387a6..b7f2ed4b1 100644 --- a/actions/admin/webhooks_old.go +++ b/actions/admin/webhooks_old.go @@ -64,7 +64,7 @@ func unsubscribeWebhookOld(c *gin.Context, _ *reqctx.AdminContext) { c.JSON(http.StatusOK, true) } -// getAllWebhooks will return all the stored webhooks +// getAllWebhooksOld will return all the stored webhooks // @DeprecatedRouter /v1/admin/webhooks/subscriptions [get] // @Summary Get All Webhooks // @Description Get All Webhooks currently subscribed to @@ -74,7 +74,7 @@ func unsubscribeWebhookOld(c *gin.Context, _ *reqctx.AdminContext) { // @Failure 500 "Internal server error - Error while getting all webhooks" // @Router /v1/admin/webhooks/subscriptions [get] // @Security x-auth-xpub -func getAllWebhooks(c *gin.Context, _ *reqctx.AdminContext) { +func getAllWebhooksOld(c *gin.Context, _ *reqctx.AdminContext) { wh, err := reqctx.Engine(c).GetWebhooks(c.Request.Context()) if err != nil { spverrors.ErrorResponse(c, err, reqctx.Logger(c)) diff --git a/actions/admin/webhooks_test.go b/actions/admin/webhooks_test.go new file mode 100644 index 000000000..1e89a465c --- /dev/null +++ b/actions/admin/webhooks_test.go @@ -0,0 +1,72 @@ +package admin_test + +import ( + "testing" + + "github.com/bitcoin-sv/spv-wallet/actions/testabilities" + testengine "github.com/bitcoin-sv/spv-wallet/engine/testabilities" +) + +func TestAdminWebhooks(t *testing.T) { + t.Run("subscribe, get and unsubscribe webhook", func(t *testing.T) { + // given: + given, then := testabilities.New(t) + cleanup := given.StartedSPVWalletWithConfiguration(testengine.WithNotificationsEnabled()) + defer cleanup() + + // and: + client := given.HttpClient().ForAdmin() + + // and: + webhook := map[string]string{ + "url": "http://localhost:8080", + "tokenHeader": "Authorization", + "tokenValue": "123", + } + + // when: + res, _ := client.R().Get("/api/v1/admin/webhooks/subscriptions") + + // then: + then.Response(res). + IsOK(). + WithJSONf(`[]`) + + // when: + res, _ = client. + R(). + SetBody(webhook). + Post("/api/v1/admin/webhooks/subscriptions") + + // then: + then.Response(res).IsOK() + + // when: + res, _ = client.R().Get("/api/v1/admin/webhooks/subscriptions") + + // then: + then.Response(res). + IsOK(). + WithJSONf(`[{ + "url": "http://localhost:8080", + "banned": false + }]`) + + // when: + res, _ = client. + R(). + SetBody(map[string]string{"url": webhook["url"]}). + Delete("/api/v1/admin/webhooks/subscriptions") + + // then: + then.Response(res).IsOK() + + // when: + res, _ = client.R().Get("/api/v1/admin/webhooks/subscriptions") + + // then: + then.Response(res). + IsOK(). + WithJSONf(`[]`) + }) +} diff --git a/docs/docs.go b/docs/docs.go index 254321881..825b7ad18 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -803,6 +803,35 @@ const docTemplate = `{ } }, "/api/v1/admin/webhooks/subscriptions": { + "get": { + "security": [ + { + "x-auth-xpub": [] + } + ], + "description": "Get All Webhooks currently subscribed to", + "produces": [ + "application/json" + ], + "tags": [ + "Admin" + ], + "summary": "Get All Webhooks", + "responses": { + "200": { + "description": "List of webhooks", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Webhook" + } + } + }, + "500": { + "description": "Internal server error - Error while getting all webhooks" + } + } + }, "post": { "security": [ { diff --git a/docs/swagger.json b/docs/swagger.json index 60bbd7988..28a4e4e04 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -794,6 +794,35 @@ } }, "/api/v1/admin/webhooks/subscriptions": { + "get": { + "security": [ + { + "x-auth-xpub": [] + } + ], + "description": "Get All Webhooks currently subscribed to", + "produces": [ + "application/json" + ], + "tags": [ + "Admin" + ], + "summary": "Get All Webhooks", + "responses": { + "200": { + "description": "List of webhooks", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Webhook" + } + } + }, + "500": { + "description": "Internal server error - Error while getting all webhooks" + } + } + }, "post": { "security": [ { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index ba435c219..cfd6262fe 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -3277,6 +3277,24 @@ paths: summary: Unsubscribe to a webhook tags: - Admin + get: + description: Get All Webhooks currently subscribed to + produces: + - application/json + responses: + "200": + description: List of webhooks + schema: + items: + $ref: '#/definitions/models.Webhook' + type: array + "500": + description: Internal server error - Error while getting all webhooks + security: + - x-auth-xpub: [] + summary: Get All Webhooks + tags: + - Admin post: description: Subscribe to a webhook to receive notifications parameters: diff --git a/engine/client.go b/engine/client.go index f03111186..9a5fff3e3 100644 --- a/engine/client.go +++ b/engine/client.go @@ -208,6 +208,10 @@ func (c *Client) Cluster() cluster.ClientInterface { // Close will safely close any open connections (cache, datastore, etc.) func (c *Client) Close(ctx context.Context) error { + // Close WebhookManager + if c.options.notifications != nil && c.options.notifications.webhookManager != nil { + c.options.notifications.webhookManager.Stop() + } // Close Datastore ds := c.Datastore() @@ -226,10 +230,6 @@ func (c *Client) Close(ctx context.Context) error { } c.options.taskManager.TaskEngine = nil } - - if c.options.notifications != nil && c.options.notifications.webhookManager != nil { - c.options.notifications.webhookManager.Stop() - } return nil } diff --git a/engine/datastore/sql.go b/engine/datastore/sql.go index 32c58d829..3b2f0bc78 100644 --- a/engine/datastore/sql.go +++ b/engine/datastore/sql.go @@ -128,6 +128,14 @@ func openSQLiteDatabase(optionalLogger glogger.Interface, config *SQLiteConfig) ); err != nil { return } + sqlDB, err := db.DB() + if err != nil { + return + } + sqlDB.SetMaxIdleConns(config.MaxIdleConnections) + sqlDB.SetMaxOpenConns(config.MaxOpenConnections) + sqlDB.SetConnMaxLifetime(config.MaxConnectionTime) + sqlDB.SetConnMaxIdleTime(config.MaxConnectionIdleTime) // Return the connection return diff --git a/engine/testabilities/fixture_configopts.go b/engine/testabilities/fixture_configopts.go index e97b3e66d..d75487bca 100644 --- a/engine/testabilities/fixture_configopts.go +++ b/engine/testabilities/fixture_configopts.go @@ -9,3 +9,9 @@ func WithNewTransactionFlowEnabled() ConfigOpts { c.ExperimentalFeatures.NewTransactionFlowEnabled = true } } + +func WithNotificationsEnabled() ConfigOpts { + return func(c *config.AppConfig) { + c.Notifications.Enabled = true + } +}