From e2947cf9a10bff56cad67c07d83dcdbe1ad0f56b Mon Sep 17 00:00:00 2001 From: Stefan Kostic Date: Wed, 13 Apr 2022 20:21:35 +0200 Subject: [PATCH 01/14] Replace math rand with crypto rand --- controllers/invoicestream.ctrl.go | 5 ++++- lib/service/invoices.go | 21 +++++++++++---------- lib/service/pubsub.go | 10 +++++++--- lib/service/user.go | 21 ++++++++++----------- lib/service/util.go | 19 +++++++++++++++++++ 5 files changed, 51 insertions(+), 25 deletions(-) create mode 100644 lib/service/util.go diff --git a/controllers/invoicestream.ctrl.go b/controllers/invoicestream.ctrl.go index 2ec80bc..e41ad63 100644 --- a/controllers/invoicestream.ctrl.go +++ b/controllers/invoicestream.ctrl.go @@ -33,7 +33,10 @@ func (controller *InvoiceStreamController) StreamInvoices(c echo.Context) error return err } invoiceChan := make(chan models.Invoice) - subId := controller.svc.InvoicePubSub.Subscribe(userId, invoiceChan) + subId, err := controller.svc.InvoicePubSub.Subscribe(userId, invoiceChan) + if err != nil { + return err + } upgrader := websocket.Upgrader{} upgrader.CheckOrigin = func(r *http.Request) bool { return true } ticker := time.NewTicker(30 * time.Second) diff --git a/lib/service/invoices.go b/lib/service/invoices.go index f0ccfd3..37f3890 100644 --- a/lib/service/invoices.go +++ b/lib/service/invoices.go @@ -7,7 +7,6 @@ import ( "errors" "fmt" "math" - "math/rand" "time" "github.com/getAlby/lndhub.go/common" @@ -141,7 +140,10 @@ func createLnRpcSendRequest(invoice *models.Invoice) (*lnrpc.SendRequest, error) }, nil } - preImage := makePreimageHex() + preImage, err := makePreimageHex() + if err != nil { + return nil, err + } pHash := sha256.New() pHash.Write(preImage) // Prepare the LNRPC call @@ -336,7 +338,10 @@ func (svc *LndhubService) AddOutgoingInvoice(ctx context.Context, userID int64, } func (svc *LndhubService) AddIncomingInvoice(ctx context.Context, userID int64, amount int64, memo, descriptionHashStr string) (*models.Invoice, error) { - preimage := makePreimageHex() + preimage, err := makePreimageHex() + if err != nil { + return nil, err + } expiry := time.Hour * 24 // invoice expires in 24h // Initialize new DB invoice invoice := models.Invoice{ @@ -350,7 +355,7 @@ func (svc *LndhubService) AddIncomingInvoice(ctx context.Context, userID int64, } // Save invoice - we save the invoice early to have a record in case the LN call fails - _, err := svc.DB.NewInsert().Model(&invoice).Exec(ctx) + _, err = svc.DB.NewInsert().Model(&invoice).Exec(ctx) if err != nil { return nil, err } @@ -395,10 +400,6 @@ func (svc *LndhubService) DecodePaymentRequest(ctx context.Context, bolt11 strin const hexBytes = random.Hex -func makePreimageHex() []byte { - b := make([]byte, 32) - for i := range b { - b[i] = hexBytes[rand.Intn(len(hexBytes))] - } - return b +func makePreimageHex() ([]byte, error) { + return randBytesFromStr(32, hexBytes) } diff --git a/lib/service/pubsub.go b/lib/service/pubsub.go index f71b853..09443bd 100644 --- a/lib/service/pubsub.go +++ b/lib/service/pubsub.go @@ -17,16 +17,20 @@ func NewPubsub() *Pubsub { return ps } -func (ps *Pubsub) Subscribe(topic int64, ch chan models.Invoice) (subId string) { +func (ps *Pubsub) Subscribe(topic int64, ch chan models.Invoice) (subId string, err error) { ps.mu.Lock() defer ps.mu.Unlock() if ps.subs[topic] == nil { ps.subs[topic] = make(map[string]chan models.Invoice) } //re-use preimage code for a uuid - subId = string(makePreimageHex()) + preImageHex, err := makePreimageHex() + if err != nil { + return "", err + } + subId = string(preImageHex) ps.subs[topic][subId] = ch - return subId + return subId, nil } func (ps *Pubsub) Unsubscribe(id string, topic int64) error { diff --git a/lib/service/user.go b/lib/service/user.go index e8bc4ac..82780ba 100644 --- a/lib/service/user.go +++ b/lib/service/user.go @@ -3,7 +3,6 @@ package service import ( "context" "database/sql" - "math/rand" "github.com/getAlby/lndhub.go/common" "github.com/getAlby/lndhub.go/db/models" @@ -18,11 +17,19 @@ func (svc *LndhubService) CreateUser(ctx context.Context, login string, password // generate user login/password if not provided user.Login = login if login == "" { - user.Login = randStringBytes(20) + randLoginBytes, err := randBytesFromStr(20, alphaNumBytes) + if err != nil { + return nil, err + } + user.Login = string(randLoginBytes) } if password == "" { - password = randStringBytes(20) + randPasswordBytes, err := randBytesFromStr(20, alphaNumBytes) + if err != nil { + return nil, err + } + password = string(randPasswordBytes) } // we only store the hashed password but return the initial plain text password in the HTTP response @@ -112,11 +119,3 @@ func (svc *LndhubService) InvoicesFor(ctx context.Context, userId int64, invoice } return invoices, nil } - -func randStringBytes(n int) string { - b := make([]byte, n) - for i := range b { - b[i] = alphaNumBytes[rand.Intn(len(alphaNumBytes))] - } - return string(b) -} diff --git a/lib/service/util.go b/lib/service/util.go new file mode 100644 index 0000000..b30c7ad --- /dev/null +++ b/lib/service/util.go @@ -0,0 +1,19 @@ +package service + +import ( + "crypto/rand" + "math/big" +) + +func randBytesFromStr(length int, from string) ([]byte, error) { + b := make([]byte, length) + fromLenBigInt := big.NewInt(int64(len(from))) + for i := range b { + r, err := rand.Int(rand.Reader, fromLenBigInt) + if err != nil { + return nil, err + } + b[i] = from[r.Int64()] + } + return b, nil +} From 5137d966cc70c0200e9d9f4bf754668e62032b5e Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Tue, 26 Apr 2022 12:14:50 +0200 Subject: [PATCH 02/14] feature: start swagger docs --- controllers/auth.ctrl.go | 14 +++ controllers/balance.ctrl.go | 11 ++- controllers/create.ctrl.go | 11 ++- docs/docs.go | 187 ++++++++++++++++++++++++++++++++++++ docs/swagger.json | 163 +++++++++++++++++++++++++++++++ docs/swagger.yaml | 106 ++++++++++++++++++++ go.mod | 14 ++- go.sum | 84 +++++++++++++--- main.go | 21 ++++ 9 files changed, 591 insertions(+), 20 deletions(-) create mode 100644 docs/docs.go create mode 100644 docs/swagger.json create mode 100644 docs/swagger.yaml diff --git a/controllers/auth.ctrl.go b/controllers/auth.ctrl.go index 618fae5..4ea127a 100644 --- a/controllers/auth.ctrl.go +++ b/controllers/auth.ctrl.go @@ -42,6 +42,20 @@ func (controller *AuthController) Auth(c echo.Context) error { return c.JSON(http.StatusBadRequest, responses.BadArgumentsError) } + if body.Login == "" || body.Password == "" { + // To support Swagger we also look in the Form data + params, err := c.FormParams() + if err != nil { + return err + } + username := params.Get("username") + password := params.Get("password") + if username != "" && password != "" { + body.Login = username + body.Password = password + } + } + accessToken, refreshToken, err := controller.svc.GenerateToken(c.Request().Context(), body.Login, body.Password, body.RefreshToken) if err != nil { return c.JSON(http.StatusUnauthorized, responses.BadAuthError) diff --git a/controllers/balance.ctrl.go b/controllers/balance.ctrl.go index 4f36aef..daf8339 100644 --- a/controllers/balance.ctrl.go +++ b/controllers/balance.ctrl.go @@ -22,7 +22,16 @@ type BalanceResponse struct { } } -// Balance : Balance Controller +// Balance godoc +// @Summary Retrieve balance +// @Description Current user's balance in satoshi +// @Accept json +// @Produce json +// @Success 200 {object} BalanceResponse +// @Failure 400 {object} responses.ErrorResponse +// @Failure 500 {object} responses.ErrorResponse +// @Router /balance [get] +// @Security OAuth2Password func (controller *BalanceController) Balance(c echo.Context) error { userId := c.Get("UserID").(int64) balance, err := controller.svc.CurrentUserBalance(c.Request().Context(), userId) diff --git a/controllers/create.ctrl.go b/controllers/create.ctrl.go index f58708c..9407b58 100644 --- a/controllers/create.ctrl.go +++ b/controllers/create.ctrl.go @@ -28,7 +28,16 @@ type CreateUserRequestBody struct { AccountType string `json:"accounttype"` } -// CreateUser : Create user Controller +// CreateUser godoc +// @Summary Create an account +// @Description Create a new account with a login and password +// @Accept json +// @Produce json +// @Param account body CreateUserRequestBody false "Create User" +// @Success 200 {object} CreateUserResponseBody +// @Failure 400 {object} responses.ErrorResponse +// @Failure 500 {object} responses.ErrorResponse +// @Router /create [post] func (controller *CreateUserController) CreateUser(c echo.Context) error { var body CreateUserRequestBody diff --git a/docs/docs.go b/docs/docs.go new file mode 100644 index 0000000..70d1cde --- /dev/null +++ b/docs/docs.go @@ -0,0 +1,187 @@ +// Package docs GENERATED BY SWAG; DO NOT EDIT +// This file was generated by swaggo/swag +package docs + +import "github.com/swaggo/swag" + +const docTemplate = `{ + "schemes": {{ marshal .Schemes }}, + "swagger": "2.0", + "info": { + "description": "{{escape .Description}}", + "title": "{{.Title}}", + "contact": { + "name": "Alby", + "url": "https://getalby.com", + "email": "hello@getalby.com" + }, + "license": { + "name": "GNU GPL", + "url": "https://www.gnu.org/licenses/gpl-3.0.en.html" + }, + "version": "{{.Version}}" + }, + "host": "{{.Host}}", + "basePath": "{{.BasePath}}", + "paths": { + "/balance": { + "get": { + "security": [ + { + "OAuth2Password": [] + } + ], + "description": "Current user's balance in satoshi", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "Retrieve balance", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.BalanceResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.ErrorResponse" + } + } + } + } + }, + "/create": { + "post": { + "description": "Create a new account with a login and password", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "Create an account", + "parameters": [ + { + "description": "Create User", + "name": "account", + "in": "body", + "schema": { + "$ref": "#/definitions/controllers.CreateUserRequestBody" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.CreateUserResponseBody" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.ErrorResponse" + } + } + } + } + } + }, + "definitions": { + "controllers.BalanceResponse": { + "type": "object", + "properties": { + "btc": { + "type": "object", + "properties": { + "availableBalance": { + "type": "integer" + } + } + } + } + }, + "controllers.CreateUserRequestBody": { + "type": "object", + "properties": { + "accounttype": { + "type": "string" + }, + "login": { + "type": "string" + }, + "partnerid": { + "type": "string" + }, + "password": { + "type": "string" + } + } + }, + "controllers.CreateUserResponseBody": { + "type": "object", + "properties": { + "login": { + "type": "string" + }, + "password": { + "type": "string" + } + } + }, + "responses.ErrorResponse": { + "type": "object", + "properties": { + "code": { + "type": "integer" + }, + "error": { + "type": "boolean" + }, + "message": { + "type": "string" + } + } + } + }, + "securityDefinitions": { + "OAuth2Password": { + "type": "oauth2", + "flow": "password", + "tokenUrl": "/auth" + } + } +}` + +// SwaggerInfo holds exported Swagger Info so clients can modify it +var SwaggerInfo = &swag.Spec{ + Version: "0.6.1", + Host: "", + BasePath: "/", + Schemes: []string{}, + Title: "LNDhub.go", + Description: "Accounting wrapper for the Lightning Network providing separate accounts for end-users.", + InfoInstanceName: "swagger", + SwaggerTemplate: docTemplate, +} + +func init() { + swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo) +} diff --git a/docs/swagger.json b/docs/swagger.json new file mode 100644 index 0000000..b8d1d21 --- /dev/null +++ b/docs/swagger.json @@ -0,0 +1,163 @@ +{ + "swagger": "2.0", + "info": { + "description": "Accounting wrapper for the Lightning Network providing separate accounts for end-users.", + "title": "LNDhub.go", + "contact": { + "name": "Alby", + "url": "https://getalby.com", + "email": "hello@getalby.com" + }, + "license": { + "name": "GNU GPL", + "url": "https://www.gnu.org/licenses/gpl-3.0.en.html" + }, + "version": "0.6.1" + }, + "basePath": "/", + "paths": { + "/balance": { + "get": { + "security": [ + { + "OAuth2Password": [] + } + ], + "description": "Current user's balance in satoshi", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "Retrieve balance", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.BalanceResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.ErrorResponse" + } + } + } + } + }, + "/create": { + "post": { + "description": "Create a new account with a login and password", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "summary": "Create an account", + "parameters": [ + { + "description": "Create User", + "name": "account", + "in": "body", + "schema": { + "$ref": "#/definitions/controllers.CreateUserRequestBody" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.CreateUserResponseBody" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.ErrorResponse" + } + } + } + } + } + }, + "definitions": { + "controllers.BalanceResponse": { + "type": "object", + "properties": { + "btc": { + "type": "object", + "properties": { + "availableBalance": { + "type": "integer" + } + } + } + } + }, + "controllers.CreateUserRequestBody": { + "type": "object", + "properties": { + "accounttype": { + "type": "string" + }, + "login": { + "type": "string" + }, + "partnerid": { + "type": "string" + }, + "password": { + "type": "string" + } + } + }, + "controllers.CreateUserResponseBody": { + "type": "object", + "properties": { + "login": { + "type": "string" + }, + "password": { + "type": "string" + } + } + }, + "responses.ErrorResponse": { + "type": "object", + "properties": { + "code": { + "type": "integer" + }, + "error": { + "type": "boolean" + }, + "message": { + "type": "string" + } + } + } + }, + "securityDefinitions": { + "OAuth2Password": { + "type": "oauth2", + "flow": "password", + "tokenUrl": "/auth" + } + } +} \ No newline at end of file diff --git a/docs/swagger.yaml b/docs/swagger.yaml new file mode 100644 index 0000000..ebc170f --- /dev/null +++ b/docs/swagger.yaml @@ -0,0 +1,106 @@ +basePath: / +definitions: + controllers.BalanceResponse: + properties: + btc: + properties: + availableBalance: + type: integer + type: object + type: object + controllers.CreateUserRequestBody: + properties: + accounttype: + type: string + login: + type: string + partnerid: + type: string + password: + type: string + type: object + controllers.CreateUserResponseBody: + properties: + login: + type: string + password: + type: string + type: object + responses.ErrorResponse: + properties: + code: + type: integer + error: + type: boolean + message: + type: string + type: object +info: + contact: + email: hello@getalby.com + name: Alby + url: https://getalby.com + description: Accounting wrapper for the Lightning Network providing separate accounts + for end-users. + license: + name: GNU GPL + url: https://www.gnu.org/licenses/gpl-3.0.en.html + title: LNDhub.go + version: 0.6.1 +paths: + /balance: + get: + consumes: + - application/json + description: Current user's balance in satoshi + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/controllers.BalanceResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/responses.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/responses.ErrorResponse' + security: + - OAuth2Password: [] + summary: Retrieve balance + /create: + post: + consumes: + - application/json + description: Create a new account with a login and password + parameters: + - description: Create User + in: body + name: account + schema: + $ref: '#/definitions/controllers.CreateUserRequestBody' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/controllers.CreateUserResponseBody' + "400": + description: Bad Request + schema: + $ref: '#/definitions/responses.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/responses.ErrorResponse' + summary: Create an account +securityDefinitions: + OAuth2Password: + flow: password + tokenUrl: /auth + type: oauth2 +swagger: "2.0" diff --git a/go.mod b/go.mod index 39fa429..511270f 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/golang-jwt/jwt v3.2.2+incompatible github.com/joho/godotenv v1.4.0 github.com/kelseyhightower/envconfig v1.4.0 - github.com/labstack/echo/v4 v4.6.1 + github.com/labstack/echo/v4 v4.7.2 github.com/labstack/gommon v0.3.1 github.com/lightningnetwork/lnd v0.14.1-beta github.com/stretchr/testify v1.7.0 @@ -21,18 +21,24 @@ require ( github.com/uptrace/bun/driver/sqliteshim v1.0.21 github.com/uptrace/bun/extra/bundebug v1.0.21 github.com/ziflex/lecho/v3 v3.1.0 - golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 + golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 google.golang.org/grpc v1.43.0 gopkg.in/macaroon.v2 v2.1.0 ) require ( + github.com/BurntSushi/toml v1.1.0 // indirect github.com/SporkHubr/echo-http-cache v0.0.0-20200706100054-1d7ae9f38029 + github.com/go-openapi/spec v0.20.5 // indirect + github.com/go-openapi/swag v0.21.1 // indirect github.com/gorilla/websocket v1.5.0 github.com/labstack/echo-contrib v0.12.0 github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e - golang.org/x/net v0.0.0-20220114011407-0dd24b26b47d // indirect - golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 // indirect + github.com/swaggo/echo-swagger v1.3.0 + github.com/swaggo/swag v1.8.1 + golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 // indirect + golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 // indirect golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba + golang.org/x/tools v0.1.10 // indirect google.golang.org/genproto v0.0.0-20220114231437-d2e6a121cae0 // indirect ) diff --git a/go.sum b/go.sum index 8153709..5fa0aca 100644 --- a/go.sum +++ b/go.sum @@ -35,8 +35,9 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= git.schwanenlied.me/yawning/bsaes.git v0.0.0-20180720073208-c0276d75487e/go.mod h1:BWqTsj8PgcPriQJGl7el20J/7TuT1d/hSyFDXMEpoEo= github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I= +github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno= github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMdUywE7VMo= @@ -44,11 +45,15 @@ github.com/DataDog/sketches-go v0.0.0-20190923095040-43f19ad77ff7/go.mod h1:Q5Db github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= +github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/NebulousLabs/fastrand v0.0.0-20181203155948-6fb6489aac4e/go.mod h1:Bdzq+51GR4/0DIhaICZEOm+OHvXGwwB2trKZ8B4Y6eQ= github.com/NebulousLabs/go-upnp v0.0.0-20180202185039-29b680b06c82/go.mod h1:GbuBk21JqF+driLX3XtJYNZjGa45YDoa9IqCTzNSfEc= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0= github.com/Shopify/sarama v1.30.0/go.mod h1:zujlQQx1kzHsh4jfV1USnptCQrHAEZ2Hk8fTKCulPVs= github.com/Shopify/toxiproxy/v2 v2.1.6-0.20210914104332-15ea381dcdae/go.mod h1:/cvHQkZ1fst0EmZnA5dFtiQdWCNCFYzb+uE2vqVgvx0= @@ -60,6 +65,8 @@ github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmH github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA= github.com/aead/siphash v1.0.1 h1:FwHfE/T45KPKYuuSAKyyvE+oPWcaQ+CUmFW0bPlM+kg= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= +github.com/agiledragon/gomonkey/v2 v2.3.1 h1:k+UnUY0EMNYUFUAQVETGY9uUTxjMdnUkP0ARyJS1zzs= +github.com/agiledragon/gomonkey/v2 v2.3.1/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaWLA6jbbgxfB4w2iY= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -72,6 +79,7 @@ github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu github.com/andybalholm/brotli v1.0.3 h1:fpcw+r1N1h0Poc1F/pHbW40cUm/lMEQslZtCkBQ0UnM= github.com/andybalholm/brotli v1.0.3/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/appleboy/gofight/v2 v2.1.2 h1:VOy3jow4vIK8BRQJoC/I9muxyYlJ2yb9ht2hZoS3rf4= github.com/appleboy/gofight/v2 v2.1.2/go.mod h1:frW+U1QZEdDgixycTj4CygQ48yLTUhplt43+Wczp3rw= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= @@ -225,8 +233,8 @@ github.com/form3tech-oss/jwt-go v3.2.3+incompatible h1:7ZaBxOI7TMoYBfyA3cQHErNNy github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/frankban/quicktest v1.0.0/go.mod h1:R98jIehRai+d1/3Hv2//jOVCTJhW1VBavT6B6CuGq2k= -github.com/frankban/quicktest v1.2.2 h1:xfmOhhoH5fGPgbEAlhLpJH9p0z/0Qizio9osmvn9IUY= github.com/frankban/quicktest v1.2.2/go.mod h1:Qh/WofXFeiAFII1aEBu529AtJo6Zg2VHscnEsbBnJ20= +github.com/frankban/quicktest v1.11.3 h1:8sXhOn0uLys67V8EsXLc6eszDs8VXWxL3iRvebPhedY= github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= @@ -252,7 +260,20 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= +github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA= +github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= +github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I= +github.com/go-openapi/spec v0.20.5 h1:skHa8av4VnAtJU5zyAUXrrdK/NDiVX8lchbG+BfcdrE= +github.com/go-openapi/spec v0.20.5/go.mod h1:QbfOSIVt3/sac+a1wzmKbbcLXm5NdZnyBZYtCijp43o= github.com/go-openapi/strfmt v0.19.5/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-openapi/swag v0.21.1 h1:wm0rhTb5z7qpJRHBdPOMuY4QjVUMbF6/kwoYeRAOrKU= +github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= @@ -468,6 +489,8 @@ github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwA github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ= github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/jrick/logrotate v1.0.0 h1:lQ1bL/n9mBNeIXoTUoYRlK4dHuNJVofX9oWqBtPnSzI= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= @@ -541,10 +564,12 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/labstack/echo-contrib v0.12.0 h1:NPr1ez+XUa5s/4LujEon+32Bxg5DO6EKSW/va06pmLc= github.com/labstack/echo-contrib v0.12.0/go.mod h1:kR62TbwsBgmpV2HVab5iQRsQtLuhPyGqCBee88XRc4M= +github.com/labstack/echo/v4 v4.1.14/go.mod h1:Q5KZ1vD3V5FEzjM79hjwVrC3ABr7F5IdM23bXQMRDGg= github.com/labstack/echo/v4 v4.1.16/go.mod h1:awO+5TzAjvL8XpibdsfXxPgHr+orhtXZJZIQCVjogKI= github.com/labstack/echo/v4 v4.5.0/go.mod h1:czIriw4a0C1dFun+ObrXp7ok03xON0N1awStJ6ArI7Y= -github.com/labstack/echo/v4 v4.6.1 h1:OMVsrnNFzYlGSdaiYGHbgWQnr+JM7NG+B9suCPie14M= github.com/labstack/echo/v4 v4.6.1/go.mod h1:RnjgMWNDB9g/HucVWhQYNQP9PvbYf6adqftqryo7s9k= +github.com/labstack/echo/v4 v4.7.2 h1:Kv2/p8OaQ+M6Ex4eGimg9b9e6icoxA42JSlOR3msKtI= +github.com/labstack/echo/v4 v4.7.2/go.mod h1:xkCDAdFCIf8jsFQ5NnbK7oqaF/yU1A1X20Ltm0OvSks= github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= github.com/labstack/gommon v0.3.1 h1:OomWaJXm7xR6L1HmEtGyQf26TEn7V6X88mktX9kee9o= github.com/labstack/gommon v0.3.1/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= @@ -587,9 +612,15 @@ github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796/go.mod h1:3p7ZTf9V1s github.com/ltcsuite/ltcutil v0.0.0-20181217130922-17f3b04680b6/go.mod h1:8Vg/LTOO0KYa/vlHWJ6XZAevPQThGH5sufO0Hrou/lA= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= @@ -602,6 +633,7 @@ github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= @@ -665,8 +697,15 @@ github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFSt github.com/opentracing/opentracing-go v1.1.1-0.20190913142402-a7454ce5950e/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/openzipkin/zipkin-go v0.3.0/go.mod h1:4c3sLeE8xjNqehmF5RpAFLPLJxXscc0R4l6Zg0P1tTQ= +github.com/otiai10/copy v1.7.0 h1:hVoPiN+t+7d2nzzwMiDHPSOogsWAStewq3TwU05+clE= +github.com/otiai10/copy v1.7.0/go.mod h1:rmRl6QPdJj6EiUqXQ/4Nn2lLXoNQjFCQbbNrxgc/t3U= +github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= +github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= +github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= +github.com/otiai10/mint v1.3.3/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM= github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pierrec/lz4/v4 v4.0.3/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pierrec/lz4/v4 v4.1.8 h1:ieHkV+i2BRzngO4Wd/3HGowuZStgq6QkPsD1eolNAO4= @@ -696,7 +735,6 @@ github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7q github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= -github.com/prometheus/common v0.26.0 h1:iMAkS2TDoNWnKM+Kopnx/8tnEStIfpYA0ur0xQzzhMQ= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4= github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= @@ -740,8 +778,8 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= @@ -773,6 +811,13 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/swaggo/echo-swagger v1.3.0 h1:xxL/4jbCY4Z3udUvqOas+IpTMKbxrKdEKwtS7He0Qhg= +github.com/swaggo/echo-swagger v1.3.0/go.mod h1:snY6MlGK+pQAfJNEfX5qaOzt/QuM/WINVxGgQaZVJgg= +github.com/swaggo/files v0.0.0-20210815190702-a29dd2bc99b2 h1:+iNTcqQJy0OZ5jk6a5NLib47eqXK8uYcPX+O4+cBpEM= +github.com/swaggo/files v0.0.0-20210815190702-a29dd2bc99b2/go.mod h1:lKJPbtWzJ9JhsTN1k1gZgleJWY/cqq0psdoMmaThG3w= +github.com/swaggo/swag v1.7.9/go.mod h1:gZ+TJ2w/Ve1RwQsA2IRoSOTidHz6DX+PIG8GWvbnoLU= +github.com/swaggo/swag v1.8.1 h1:JuARzFX1Z1njbCGz+ZytBR15TFJwF2Q7fu8puJHhQYI= +github.com/swaggo/swag v1.8.1/go.mod h1:ugemnJsPZm/kRwFUnzBlbHRd0JY9zE1M4F+uy2pAaPQ= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 h1:uruHq4dN7GR16kFc5fp3d1RIYzJW5onx8Ybykw2YQFA= @@ -902,8 +947,8 @@ go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= @@ -947,8 +992,10 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210920023735-84f357641f63/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA= +golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -987,8 +1034,9 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38= golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o= +golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1033,6 +1081,7 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= @@ -1041,8 +1090,9 @@ golang.org/x/net v0.0.0-20210917221730-978cfadd31cf/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20211008194852-3b03d305991f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220114011407-0dd24b26b47d h1:1n1fc535VhN8SYtD4cDUyNlfpAF2ROMM9+11equK3hs= -golang.org/x/net v0.0.0-20220114011407-0dd24b26b47d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1119,6 +1169,7 @@ golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210426080607-c94f62235c83/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1136,11 +1187,13 @@ golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211123173158-ef496fb156ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 h1:XfKQ4OlFl8okEOr5UvAqFRVj8pY/4yfcXrddB8qAbU0= -golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 h1:xHms4gcpe1YE7A3yIllJXP16CMAGuqwO2lX1mTyyRRc= +golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1221,8 +1274,10 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= -golang.org/x/tools v0.1.8 h1:P1HhGGuLW4aAclzjtmJdf0mJOjVUZUzOTqkAkWL+l6w= golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= +golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= +golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20= +golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1371,6 +1426,7 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/main.go b/main.go index 25e072b..252f496 100644 --- a/main.go +++ b/main.go @@ -15,6 +15,7 @@ import ( "github.com/getAlby/lndhub.go/controllers" "github.com/getAlby/lndhub.go/db" "github.com/getAlby/lndhub.go/db/migrations" + "github.com/getAlby/lndhub.go/docs" "github.com/getAlby/lndhub.go/lib" "github.com/getAlby/lndhub.go/lib/responses" "github.com/getAlby/lndhub.go/lib/service" @@ -29,6 +30,7 @@ import ( "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" "github.com/lightningnetwork/lnd/lnrpc" + echoSwagger "github.com/swaggo/echo-swagger" "github.com/uptrace/bun/migrate" "github.com/ziflex/lecho/v3" "golang.org/x/time/rate" @@ -40,6 +42,21 @@ var indexHtml string //go:embed static/* var staticContent embed.FS +// @title LNDhub.go +// @version 0.6.1 +// @description Accounting wrapper for the Lightning Network providing separate accounts for end-users. + +// @contact.name Alby +// @contact.url https://getalby.com +// @contact.email hello@getalby.com + +// @license.name GNU GPL +// @license.url https://www.gnu.org/licenses/gpl-3.0.en.html + +// @BasePath / + +// @securitydefinitions.oauth2.password OAuth2Password +// @tokenUrl /auth func main() { c := &service.Config{} @@ -165,6 +182,10 @@ func main() { //Authentication should be done through the query param because this is a websocket e.GET("/invoices/stream", controllers.NewInvoiceStreamController(svc).StreamInvoices) + //Swagger API spec + docs.SwaggerInfo.Host = "localhost:3000" + e.GET("/swagger/*", echoSwagger.WrapHandler) + // Subscribe to LND invoice updates in the background go svc.InvoiceUpdateSubscription(context.Background()) From 0818abdd2c01f1c359630853c0341ead578da77b Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Tue, 26 Apr 2022 12:21:16 +0200 Subject: [PATCH 03/14] change tags --- controllers/balance.ctrl.go | 1 + controllers/create.ctrl.go | 1 + docs/docs.go | 3 +++ docs/swagger.json | 3 +++ docs/swagger.yaml | 2 ++ 5 files changed, 10 insertions(+) diff --git a/controllers/balance.ctrl.go b/controllers/balance.ctrl.go index daf8339..d6b05cb 100644 --- a/controllers/balance.ctrl.go +++ b/controllers/balance.ctrl.go @@ -27,6 +27,7 @@ type BalanceResponse struct { // @Description Current user's balance in satoshi // @Accept json // @Produce json +// @Tags Account // @Success 200 {object} BalanceResponse // @Failure 400 {object} responses.ErrorResponse // @Failure 500 {object} responses.ErrorResponse diff --git a/controllers/create.ctrl.go b/controllers/create.ctrl.go index 9407b58..f2cc0fd 100644 --- a/controllers/create.ctrl.go +++ b/controllers/create.ctrl.go @@ -33,6 +33,7 @@ type CreateUserRequestBody struct { // @Description Create a new account with a login and password // @Accept json // @Produce json +// @Tags Account // @Param account body CreateUserRequestBody false "Create User" // @Success 200 {object} CreateUserResponseBody // @Failure 400 {object} responses.ErrorResponse diff --git a/docs/docs.go b/docs/docs.go index 70d1cde..5a56f9d 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -38,6 +38,9 @@ const docTemplate = `{ "produces": [ "application/json" ], + "tags": [ + "account" + ], "summary": "Retrieve balance", "responses": { "200": { diff --git a/docs/swagger.json b/docs/swagger.json index b8d1d21..5d14f0a 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -30,6 +30,9 @@ "produces": [ "application/json" ], + "tags": [ + "account" + ], "summary": "Retrieve balance", "responses": { "200": { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index ebc170f..b3ba793 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -71,6 +71,8 @@ paths: security: - OAuth2Password: [] summary: Retrieve balance + tags: + - account /create: post: consumes: From 2793303ad30313b0b2e4a40aac97d602ad78e4a7 Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Tue, 26 Apr 2022 13:48:05 +0200 Subject: [PATCH 04/14] swagger: add all endpoints --- controllers/addinvoice.ctrl.go | 13 +- controllers/auth.ctrl.go | 12 +- controllers/balance.ctrl.go | 8 +- controllers/checkpayment.ctrl.go | 13 +- controllers/getinfo.ctrl.go | 70 ++- controllers/gettxs.ctrl.go | 23 +- controllers/invoice.ctrl.go | 13 +- controllers/invoicestream.ctrl.go | 15 +- controllers/keysend.ctrl.go | 13 +- controllers/payinvoice.ctrl.go | 13 +- docs/docs.go | 851 ++++++++++++++++++++++++++++- docs/swagger.json | 853 +++++++++++++++++++++++++++++- docs/swagger.yaml | 568 +++++++++++++++++++- main.go | 7 +- 14 files changed, 2451 insertions(+), 21 deletions(-) diff --git a/controllers/addinvoice.ctrl.go b/controllers/addinvoice.ctrl.go index d15b19b..cd8d9a1 100644 --- a/controllers/addinvoice.ctrl.go +++ b/controllers/addinvoice.ctrl.go @@ -30,7 +30,18 @@ type AddInvoiceResponseBody struct { PayReq string `json:"pay_req"` } -// AddInvoice : Add invoice Controller +// AddInvoice godoc +// @Summary Generate a new invoice +// @Description Returns a new bolt11 invoice +// @Accept json +// @Produce json +// @Tags Invoice +// @Param invoice body AddInvoiceRequestBody True "Add Invoice" +// @Success 200 {object} AddInvoiceResponseBody +// @Failure 400 {object} responses.ErrorResponse +// @Failure 500 {object} responses.ErrorResponse +// @Router /addinvoice [post] +// @Security OAuth2Password func (controller *AddInvoiceController) AddInvoice(c echo.Context) error { userID := c.Get("UserID").(int64) return AddInvoice(c, controller.svc, userID) diff --git a/controllers/auth.ctrl.go b/controllers/auth.ctrl.go index 4ea127a..7848034 100644 --- a/controllers/auth.ctrl.go +++ b/controllers/auth.ctrl.go @@ -29,7 +29,17 @@ type AuthResponseBody struct { AccessToken string `json:"access_token"` } -// Auth : Auth Controller +// Auth godoc +// @Summary Authenticate +// @Description Exchanges a login + password for a token +// @Accept json +// @Produce json +// @Tags Account +// @Param AuthRequestBody body AuthRequestBody false "Login and password" +// @Success 200 {object} AuthResponseBody +// @Failure 400 {object} responses.ErrorResponse +// @Failure 500 {object} responses.ErrorResponse +// @Router /auth [post] func (controller *AuthController) Auth(c echo.Context) error { var body AuthRequestBody diff --git a/controllers/balance.ctrl.go b/controllers/balance.ctrl.go index d6b05cb..3fa813f 100644 --- a/controllers/balance.ctrl.go +++ b/controllers/balance.ctrl.go @@ -28,11 +28,11 @@ type BalanceResponse struct { // @Accept json // @Produce json // @Tags Account -// @Success 200 {object} BalanceResponse -// @Failure 400 {object} responses.ErrorResponse -// @Failure 500 {object} responses.ErrorResponse +// @Success 200 {object} BalanceResponse +// @Failure 400 {object} responses.ErrorResponse +// @Failure 500 {object} responses.ErrorResponse // @Router /balance [get] -// @Security OAuth2Password +// @Security OAuth2Password func (controller *BalanceController) Balance(c echo.Context) error { userId := c.Get("UserID").(int64) balance, err := controller.svc.CurrentUserBalance(c.Request().Context(), userId) diff --git a/controllers/checkpayment.ctrl.go b/controllers/checkpayment.ctrl.go index c280058..3c17c02 100644 --- a/controllers/checkpayment.ctrl.go +++ b/controllers/checkpayment.ctrl.go @@ -21,7 +21,18 @@ func NewCheckPaymentController(svc *service.LndhubService) *CheckPaymentControll return &CheckPaymentController{svc: svc} } -// CheckPayment : Check Payment Controller +// CheckPayment godoc +// @Summary Check if an invoice is paid +// @Description Checks if an invoice is paid, can be incoming our outgoing +// @Accept json +// @Produce json +// @Tags Invoice +// @Param payment_hash path string true "Payment hash" +// @Success 200 {object} CheckPaymentResponseBody +// @Failure 400 {object} responses.ErrorResponse +// @Failure 500 {object} responses.ErrorResponse +// @Router /checkpayment/{payment_hash} [get] +// @Security OAuth2Password func (controller *CheckPaymentController) CheckPayment(c echo.Context) error { userId := c.Get("UserID").(int64) rHash := c.Param("payment_hash") diff --git a/controllers/getinfo.ctrl.go b/controllers/getinfo.ctrl.go index c4a6b33..d8c118e 100644 --- a/controllers/getinfo.ctrl.go +++ b/controllers/getinfo.ctrl.go @@ -7,6 +7,63 @@ import ( "github.com/labstack/echo/v4" ) +//Copy over struct for swagger purposes +type GetInfoResponse struct { + // The version of the LND software that the node is running. + Version string `protobuf:"bytes,14,opt,name=version,proto3" json:"version,omitempty"` + // The SHA1 commit hash that the daemon is compiled with. + CommitHash string `protobuf:"bytes,20,opt,name=commit_hash,json=commitHash,proto3" json:"commit_hash,omitempty"` + // The identity pubkey of the current node. + IdentityPubkey string `protobuf:"bytes,1,opt,name=identity_pubkey,json=identityPubkey,proto3" json:"identity_pubkey,omitempty"` + // If applicable, the alias of the current node, e.g. "bob" + Alias string `protobuf:"bytes,2,opt,name=alias,proto3" json:"alias,omitempty"` + // The color of the current node in hex code format + Color string `protobuf:"bytes,17,opt,name=color,proto3" json:"color,omitempty"` + // Number of pending channels + NumPendingChannels uint32 `protobuf:"varint,3,opt,name=num_pending_channels,json=numPendingChannels,proto3" json:"num_pending_channels,omitempty"` + // Number of active channels + NumActiveChannels uint32 `protobuf:"varint,4,opt,name=num_active_channels,json=numActiveChannels,proto3" json:"num_active_channels,omitempty"` + // Number of inactive channels + NumInactiveChannels uint32 `protobuf:"varint,15,opt,name=num_inactive_channels,json=numInactiveChannels,proto3" json:"num_inactive_channels,omitempty"` + // Number of peers + NumPeers uint32 `protobuf:"varint,5,opt,name=num_peers,json=numPeers,proto3" json:"num_peers,omitempty"` + // The node's current view of the height of the best block + BlockHeight uint32 `protobuf:"varint,6,opt,name=block_height,json=blockHeight,proto3" json:"block_height,omitempty"` + // The node's current view of the hash of the best block + BlockHash string `protobuf:"bytes,8,opt,name=block_hash,json=blockHash,proto3" json:"block_hash,omitempty"` + // Timestamp of the block best known to the wallet + BestHeaderTimestamp int64 `protobuf:"varint,13,opt,name=best_header_timestamp,json=bestHeaderTimestamp,proto3" json:"best_header_timestamp,omitempty"` + // Whether the wallet's view is synced to the main chain + SyncedToChain bool `protobuf:"varint,9,opt,name=synced_to_chain,json=syncedToChain,proto3" json:"synced_to_chain,omitempty"` + // Whether we consider ourselves synced with the public channel graph. + SyncedToGraph bool `protobuf:"varint,18,opt,name=synced_to_graph,json=syncedToGraph,proto3" json:"synced_to_graph,omitempty"` + // + //Whether the current node is connected to testnet. This field is + //deprecated and the network field should be used instead + // + // Deprecated: Do not use. + Testnet bool `protobuf:"varint,10,opt,name=testnet,proto3" json:"testnet,omitempty"` + // A list of active chains the node is connected to + Chains []*Chain `protobuf:"bytes,16,rep,name=chains,proto3" json:"chains,omitempty"` + // The URIs of the current node. + Uris []string `protobuf:"bytes,12,rep,name=uris,proto3" json:"uris,omitempty"` + // + //Features that our node has advertised in our init message, node + //announcements and invoices. + Features map[uint32]*Feature `protobuf:"bytes,19,rep,name=features,proto3" json:"features,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` +} +type Chain struct { + // The blockchain the node is on (eg bitcoin, litecoin) + Chain string `protobuf:"bytes,1,opt,name=chain,proto3" json:"chain,omitempty"` + // The network the node is on (eg regtest, testnet, mainnet) + Network string `protobuf:"bytes,2,opt,name=network,proto3" json:"network,omitempty"` +} +type Feature struct { + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + IsRequired bool `protobuf:"varint,3,opt,name=is_required,json=isRequired,proto3" json:"is_required,omitempty"` + IsKnown bool `protobuf:"varint,4,opt,name=is_known,json=isKnown,proto3" json:"is_known,omitempty"` +} + // GetInfoController : GetInfoController struct type GetInfoController struct { svc *service.LndhubService @@ -16,10 +73,19 @@ func NewGetInfoController(svc *service.LndhubService) *GetInfoController { return &GetInfoController{svc: svc} } -// GetInfo : GetInfo handler +// GetInfo godoc +// @Summary Get info about the Lightning node +// @Description Returns info about the backend node powering this LNDhub instance +// @Accept json +// @Produce json +// @Tags Info +// @Success 200 {object} GetInfoResponse +// @Failure 400 {object} responses.ErrorResponse +// @Failure 500 {object} responses.ErrorResponse +// @Router /getinfo [get] +// @Security OAuth2Password func (controller *GetInfoController) GetInfo(c echo.Context) error { - // TODO: add some caching for this GetInfo call. No need to always hit the node info, err := controller.svc.GetInfo(c.Request().Context()) if err != nil { return err diff --git a/controllers/gettxs.ctrl.go b/controllers/gettxs.ctrl.go index 4d5cd04..4fff99d 100644 --- a/controllers/gettxs.ctrl.go +++ b/controllers/gettxs.ctrl.go @@ -42,7 +42,17 @@ type IncomingInvoice struct { IsPaid bool `json:"ispaid"` } -// GetTXS : Get TXS Controller +// GetTXS godoc +// @Summary Retrieve outgoing payments +// @Description Returns a list of outgoing payments for a user +// @Accept json +// @Produce json +// @Tags Account +// @Success 200 {object} []OutgoingInvoice +// @Failure 400 {object} responses.ErrorResponse +// @Failure 500 {object} responses.ErrorResponse +// @Router /gettxs [get] +// @Security OAuth2Password func (controller *GetTXSController) GetTXS(c echo.Context) error { userId := c.Get("UserID").(int64) @@ -68,6 +78,17 @@ func (controller *GetTXSController) GetTXS(c echo.Context) error { return c.JSON(http.StatusOK, &response) } +// GetUserInvoices godoc +// @Summary Retrieve incoming invoices +// @Description Returns a list of incoming invoices for a user +// @Accept json +// @Produce json +// @Tags Account +// @Success 200 {object} []IncomingInvoice +// @Failure 400 {object} responses.ErrorResponse +// @Failure 500 {object} responses.ErrorResponse +// @Router /getuserinvoices [get] +// @Security OAuth2Password func (controller *GetTXSController) GetUserInvoices(c echo.Context) error { userId := c.Get("UserID").(int64) diff --git a/controllers/invoice.ctrl.go b/controllers/invoice.ctrl.go index f0a720b..90333dd 100644 --- a/controllers/invoice.ctrl.go +++ b/controllers/invoice.ctrl.go @@ -17,7 +17,18 @@ func NewInvoiceController(svc *service.LndhubService) *InvoiceController { return &InvoiceController{svc: svc} } -// Invoice : Invoice Controller +// Invoice godoc +// @Summary Generate a new invoice +// @Description Returns a new bolt11 invoice for a user with given login, without an Authorization Header +// @Accept json +// @Produce json +// @Tags Invoice +// @Param user_login path string true "User Login" +// @Param invoice body AddInvoiceRequestBody True "Add Invoice" +// @Success 200 {object} AddInvoiceResponseBody +// @Failure 400 {object} responses.ErrorResponse +// @Failure 500 {object} responses.ErrorResponse +// @Router /invoice/{user_login} [post] func (controller *InvoiceController) Invoice(c echo.Context) error { user, err := controller.svc.FindUserByLogin(c.Request().Context(), c.Param("user_login")) if err != nil { diff --git a/controllers/invoicestream.ctrl.go b/controllers/invoicestream.ctrl.go index 2f1e0bd..d48f467 100644 --- a/controllers/invoicestream.ctrl.go +++ b/controllers/invoicestream.ctrl.go @@ -26,7 +26,20 @@ func NewInvoiceStreamController(svc *service.LndhubService) *InvoiceStreamContro return &InvoiceStreamController{svc: svc} } -// Stream invoices streams incoming payments to the client +// StreamInvoices godoc +// @Summary Websocket for incoming payments +// @Description Websocket: won't work with Swagger web UI. Returns a stream of settled incoming payments. +// @Description A keep-alive message is sent on startup and every 30s. +// @Accept json +// @Produce json +// @Tags Invoice +// @Param token query string true "Auth token, retrieved from /auth endpoint" +// @Param since_payment_hash query string false "Payment hash of earliest invoice. If specified, missing updates starting from this payment will be sent." +// @Success 200 {object} []InvoiceEventWrapper +// @Failure 400 {object} responses.ErrorResponse +// @Failure 500 {object} responses.ErrorResponse +// @Router /invoices/stream [get] +// @Security OAuth2Password func (controller *InvoiceStreamController) StreamInvoices(c echo.Context) error { userId, err := tokens.ParseToken(controller.svc.Config.JWTSecret, (c.QueryParam("token")), false) if err != nil { diff --git a/controllers/keysend.ctrl.go b/controllers/keysend.ctrl.go index 711ba83..abeaa57 100644 --- a/controllers/keysend.ctrl.go +++ b/controllers/keysend.ctrl.go @@ -41,7 +41,18 @@ type KeySendResponseBody struct { PaymentRoute *service.Route `json:"payment_route,omitempty"` } -// KeySend : Key send Controller +//// PayInvoice godoc +// @Summary Make a keysend payment +// @Description Pay a node without an invoice using it's public key +// @Accept json +// @Produce json +// @Tags Payment +// @Param KeySendRequestBody body KeySendRequestBody True "Invoice to pay" +// @Success 200 {object} KeySendResponseBody +// @Failure 400 {object} responses.ErrorResponse +// @Failure 500 {object} responses.ErrorResponse +// @Router /keysend [post] +// @Security OAuth2Password func (controller *KeySendController) KeySend(c echo.Context) error { userID := c.Get("UserID").(int64) reqBody := KeySendRequestBody{} diff --git a/controllers/payinvoice.ctrl.go b/controllers/payinvoice.ctrl.go index 548a2be..de2c36e 100644 --- a/controllers/payinvoice.ctrl.go +++ b/controllers/payinvoice.ctrl.go @@ -38,7 +38,18 @@ type PayInvoiceResponseBody struct { PaymentRoute *service.Route `json:"payment_route,omitempty"` } -// PayInvoice : Pay invoice Controller +// PayInvoice godoc +// @Summary Pay an invoice +// @Description Pay a bolt11 invoice +// @Accept json +// @Produce json +// @Tags Payment +// @Param PayInvoiceRequest body PayInvoiceRequestBody True "Invoice to pay" +// @Success 200 {object} PayInvoiceResponseBody +// @Failure 400 {object} responses.ErrorResponse +// @Failure 500 {object} responses.ErrorResponse +// @Router /payinvoice [post] +// @Security OAuth2Password func (controller *PayInvoiceController) PayInvoice(c echo.Context) error { userID := c.Get("UserID").(int64) reqBody := PayInvoiceRequestBody{} diff --git a/docs/docs.go b/docs/docs.go index 5a56f9d..01511e1 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -24,6 +24,102 @@ const docTemplate = `{ "host": "{{.Host}}", "basePath": "{{.BasePath}}", "paths": { + "/addinvoice": { + "post": { + "security": [ + { + "OAuth2Password": [] + } + ], + "description": "Returns a new bolt11 invoice", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Invoice" + ], + "summary": "Generate a new invoice", + "parameters": [ + { + "description": "Add Invoice", + "name": "invoice", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/controllers.AddInvoiceRequestBody" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.AddInvoiceResponseBody" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.ErrorResponse" + } + } + } + } + }, + "/auth": { + "post": { + "description": "Exchanges a login + password for a token", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Account" + ], + "summary": "Authenticate", + "parameters": [ + { + "description": "Login and password", + "name": "AuthRequestBody", + "in": "body", + "schema": { + "$ref": "#/definitions/controllers.AuthRequestBody" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.AuthResponseBody" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.ErrorResponse" + } + } + } + } + }, "/balance": { "get": { "security": [ @@ -39,7 +135,7 @@ const docTemplate = `{ "application/json" ], "tags": [ - "account" + "Account" ], "summary": "Retrieve balance", "responses": { @@ -64,6 +160,55 @@ const docTemplate = `{ } } }, + "/checkpayment/{payment_hash}": { + "get": { + "security": [ + { + "OAuth2Password": [] + } + ], + "description": "Checks if an invoice is paid, can be incoming our outgoing", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Invoice" + ], + "summary": "Check if an invoice is paid", + "parameters": [ + { + "type": "string", + "description": "Payment hash", + "name": "payment_hash", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.CheckPaymentResponseBody" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.ErrorResponse" + } + } + } + } + }, "/create": { "post": { "description": "Create a new account with a login and password", @@ -73,6 +218,9 @@ const docTemplate = `{ "produces": [ "application/json" ], + "tags": [ + "Account" + ], "summary": "Create an account", "parameters": [ { @@ -105,9 +253,401 @@ const docTemplate = `{ } } } + }, + "/getinfo": { + "get": { + "security": [ + { + "OAuth2Password": [] + } + ], + "description": "Returns info about the backend node powering this LNDhub instance", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Info" + ], + "summary": "Get info about the Lightning node", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.GetInfoResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.ErrorResponse" + } + } + } + } + }, + "/gettxs": { + "get": { + "security": [ + { + "OAuth2Password": [] + } + ], + "description": "Returns a list of outgoing payments for a user", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Account" + ], + "summary": "Retrieve outgoing payments", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/controllers.OutgoingInvoice" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.ErrorResponse" + } + } + } + } + }, + "/getuserinvoices": { + "get": { + "security": [ + { + "OAuth2Password": [] + } + ], + "description": "Returns a list of incoming invoices for a user", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Account" + ], + "summary": "Retrieve incoming invoices", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/controllers.IncomingInvoice" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.ErrorResponse" + } + } + } + } + }, + "/invoice/{user_login}": { + "post": { + "description": "Returns a new bolt11 invoice for a user with given login, without an Authorization Header", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Invoice" + ], + "summary": "Generate a new invoice", + "parameters": [ + { + "type": "string", + "description": "User Login", + "name": "user_login", + "in": "path", + "required": true + }, + { + "description": "Add Invoice", + "name": "invoice", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/controllers.AddInvoiceRequestBody" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.AddInvoiceResponseBody" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.ErrorResponse" + } + } + } + } + }, + "/invoices/stream": { + "get": { + "security": [ + { + "OAuth2Password": [] + } + ], + "description": "Websocket: won't work with Swagger web UI. Returns a stream of settled incoming payments.\nA keep-alive message is sent on startup and every 30s.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Invoice" + ], + "summary": "Websocket for incoming payments", + "parameters": [ + { + "type": "string", + "description": "Auth token, retrieved from /auth endpoint", + "name": "token", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "Payment hash of earliest invoice. If specified, missing updates starting from this payment will be sent.", + "name": "since_payment_hash", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/controllers.InvoiceEventWrapper" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.ErrorResponse" + } + } + } + } + }, + "/keysend": { + "post": { + "security": [ + { + "OAuth2Password": [] + } + ], + "description": "Pay a node without an invoice using it's public key", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Payment" + ], + "summary": "Make a keysend payment", + "parameters": [ + { + "description": "Invoice to pay", + "name": "KeySendRequestBody", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/controllers.KeySendRequestBody" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.KeySendResponseBody" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.ErrorResponse" + } + } + } + } + }, + "/payinvoice": { + "post": { + "security": [ + { + "OAuth2Password": [] + } + ], + "description": "Pay a bolt11 invoice", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Payment" + ], + "summary": "Pay an invoice", + "parameters": [ + { + "description": "Invoice to pay", + "name": "PayInvoiceRequest", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/controllers.PayInvoiceRequestBody" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.PayInvoiceResponseBody" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.ErrorResponse" + } + } + } + } } }, "definitions": { + "controllers.AddInvoiceRequestBody": { + "type": "object", + "properties": { + "amt": { + "description": "amount in Satoshi" + }, + "description_hash": { + "type": "string" + }, + "memo": { + "type": "string" + } + } + }, + "controllers.AddInvoiceResponseBody": { + "type": "object", + "properties": { + "pay_req": { + "type": "string" + }, + "payment_request": { + "type": "string" + }, + "r_hash": { + "type": "string" + } + } + }, + "controllers.AuthRequestBody": { + "type": "object", + "properties": { + "login": { + "type": "string" + }, + "password": { + "type": "string" + }, + "refresh_token": { + "type": "string" + } + } + }, + "controllers.AuthResponseBody": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "refresh_token": { + "type": "string" + } + } + }, "controllers.BalanceResponse": { "type": "object", "properties": { @@ -121,6 +661,27 @@ const docTemplate = `{ } } }, + "controllers.Chain": { + "type": "object", + "properties": { + "chain": { + "description": "The blockchain the node is on (eg bitcoin, litecoin)", + "type": "string" + }, + "network": { + "description": "The network the node is on (eg regtest, testnet, mainnet)", + "type": "string" + } + } + }, + "controllers.CheckPaymentResponseBody": { + "type": "object", + "properties": { + "paid": { + "type": "boolean" + } + } + }, "controllers.CreateUserRequestBody": { "type": "object", "properties": { @@ -149,6 +710,281 @@ const docTemplate = `{ } } }, + "controllers.Feature": { + "type": "object", + "properties": { + "is_known": { + "type": "boolean" + }, + "is_required": { + "type": "boolean" + }, + "name": { + "type": "string" + } + } + }, + "controllers.GetInfoResponse": { + "type": "object", + "properties": { + "alias": { + "description": "If applicable, the alias of the current node, e.g. \"bob\"", + "type": "string" + }, + "best_header_timestamp": { + "description": "Timestamp of the block best known to the wallet", + "type": "integer" + }, + "block_hash": { + "description": "The node's current view of the hash of the best block", + "type": "string" + }, + "block_height": { + "description": "The node's current view of the height of the best block", + "type": "integer" + }, + "chains": { + "description": "A list of active chains the node is connected to", + "type": "array", + "items": { + "$ref": "#/definitions/controllers.Chain" + } + }, + "color": { + "description": "The color of the current node in hex code format", + "type": "string" + }, + "commit_hash": { + "description": "The SHA1 commit hash that the daemon is compiled with.", + "type": "string" + }, + "features": { + "description": "Features that our node has advertised in our init message, node\nannouncements and invoices.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/controllers.Feature" + } + }, + "identity_pubkey": { + "description": "The identity pubkey of the current node.", + "type": "string" + }, + "num_active_channels": { + "description": "Number of active channels", + "type": "integer" + }, + "num_inactive_channels": { + "description": "Number of inactive channels", + "type": "integer" + }, + "num_peers": { + "description": "Number of peers", + "type": "integer" + }, + "num_pending_channels": { + "description": "Number of pending channels", + "type": "integer" + }, + "synced_to_chain": { + "description": "Whether the wallet's view is synced to the main chain", + "type": "boolean" + }, + "synced_to_graph": { + "description": "Whether we consider ourselves synced with the public channel graph.", + "type": "boolean" + }, + "testnet": { + "description": "Whether the current node is connected to testnet. This field is\ndeprecated and the network field should be used instead\n\nDeprecated: Do not use.", + "type": "boolean" + }, + "uris": { + "description": "The URIs of the current node.", + "type": "array", + "items": { + "type": "string" + } + }, + "version": { + "description": "The version of the LND software that the node is running.", + "type": "string" + } + } + }, + "controllers.IncomingInvoice": { + "type": "object", + "properties": { + "amt": { + "type": "integer" + }, + "description": { + "type": "string" + }, + "expire_time": { + "type": "integer" + }, + "ispaid": { + "type": "boolean" + }, + "pay_req": { + "type": "string" + }, + "payment_hash": {}, + "payment_request": { + "type": "string" + }, + "r_hash": {}, + "timestamp": { + "type": "integer" + }, + "type": { + "type": "string" + } + } + }, + "controllers.InvoiceEventWrapper": { + "type": "object", + "properties": { + "invoice": { + "$ref": "#/definitions/controllers.IncomingInvoice" + }, + "type": { + "type": "string" + } + } + }, + "controllers.KeySendRequestBody": { + "type": "object", + "required": [ + "amount", + "destination" + ], + "properties": { + "amount": { + "type": "integer" + }, + "customRecords": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "destination": { + "type": "string" + }, + "memo": { + "type": "string" + } + } + }, + "controllers.KeySendResponseBody": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "description_hash": { + "type": "string" + }, + "destination": { + "type": "string" + }, + "num_satoshis": { + "type": "integer" + }, + "payment_error": { + "type": "string" + }, + "payment_hash": { + "$ref": "#/definitions/lib.JavaScriptBuffer" + }, + "payment_preimage": { + "$ref": "#/definitions/lib.JavaScriptBuffer" + }, + "payment_route": { + "$ref": "#/definitions/service.Route" + } + } + }, + "controllers.OutgoingInvoice": { + "type": "object", + "properties": { + "fee": { + "type": "integer" + }, + "memo": { + "type": "string" + }, + "payment_hash": {}, + "payment_preimage": { + "type": "string" + }, + "r_hash": {}, + "timestamp": { + "type": "integer" + }, + "type": { + "type": "string" + }, + "value": { + "type": "integer" + } + } + }, + "controllers.PayInvoiceRequestBody": { + "type": "object", + "required": [ + "invoice" + ], + "properties": { + "amount": {}, + "invoice": { + "type": "string" + } + } + }, + "controllers.PayInvoiceResponseBody": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "description_hash": { + "type": "string" + }, + "num_satoshis": { + "type": "integer" + }, + "pay_req": { + "type": "string" + }, + "payment_error": { + "type": "string" + }, + "payment_hash": { + "$ref": "#/definitions/lib.JavaScriptBuffer" + }, + "payment_preimage": { + "$ref": "#/definitions/lib.JavaScriptBuffer" + }, + "payment_request": { + "type": "string" + }, + "payment_route": { + "$ref": "#/definitions/service.Route" + } + } + }, + "lib.JavaScriptBuffer": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "integer" + } + } + } + }, "responses.ErrorResponse": { "type": "object", "properties": { @@ -162,6 +998,17 @@ const docTemplate = `{ "type": "string" } } + }, + "service.Route": { + "type": "object", + "properties": { + "total_amt": { + "type": "integer" + }, + "total_fees": { + "type": "integer" + } + } } }, "securityDefinitions": { @@ -178,7 +1025,7 @@ var SwaggerInfo = &swag.Spec{ Version: "0.6.1", Host: "", BasePath: "/", - Schemes: []string{}, + Schemes: []string{"http", "https"}, Title: "LNDhub.go", Description: "Accounting wrapper for the Lightning Network providing separate accounts for end-users.", InfoInstanceName: "swagger", diff --git a/docs/swagger.json b/docs/swagger.json index 5d14f0a..5679125 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -1,4 +1,8 @@ { + "schemes": [ + "http", + "https" + ], "swagger": "2.0", "info": { "description": "Accounting wrapper for the Lightning Network providing separate accounts for end-users.", @@ -16,6 +20,102 @@ }, "basePath": "/", "paths": { + "/addinvoice": { + "post": { + "security": [ + { + "OAuth2Password": [] + } + ], + "description": "Returns a new bolt11 invoice", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Invoice" + ], + "summary": "Generate a new invoice", + "parameters": [ + { + "description": "Add Invoice", + "name": "invoice", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/controllers.AddInvoiceRequestBody" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.AddInvoiceResponseBody" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.ErrorResponse" + } + } + } + } + }, + "/auth": { + "post": { + "description": "Exchanges a login + password for a token", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Account" + ], + "summary": "Authenticate", + "parameters": [ + { + "description": "Login and password", + "name": "AuthRequestBody", + "in": "body", + "schema": { + "$ref": "#/definitions/controllers.AuthRequestBody" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.AuthResponseBody" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.ErrorResponse" + } + } + } + } + }, "/balance": { "get": { "security": [ @@ -31,7 +131,7 @@ "application/json" ], "tags": [ - "account" + "Account" ], "summary": "Retrieve balance", "responses": { @@ -56,6 +156,55 @@ } } }, + "/checkpayment/{payment_hash}": { + "get": { + "security": [ + { + "OAuth2Password": [] + } + ], + "description": "Checks if an invoice is paid, can be incoming our outgoing", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Invoice" + ], + "summary": "Check if an invoice is paid", + "parameters": [ + { + "type": "string", + "description": "Payment hash", + "name": "payment_hash", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.CheckPaymentResponseBody" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.ErrorResponse" + } + } + } + } + }, "/create": { "post": { "description": "Create a new account with a login and password", @@ -65,6 +214,9 @@ "produces": [ "application/json" ], + "tags": [ + "Account" + ], "summary": "Create an account", "parameters": [ { @@ -97,9 +249,401 @@ } } } + }, + "/getinfo": { + "get": { + "security": [ + { + "OAuth2Password": [] + } + ], + "description": "Returns info about the backend node powering this LNDhub instance", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Info" + ], + "summary": "Get info about the Lightning node", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.GetInfoResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.ErrorResponse" + } + } + } + } + }, + "/gettxs": { + "get": { + "security": [ + { + "OAuth2Password": [] + } + ], + "description": "Returns a list of outgoing payments for a user", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Account" + ], + "summary": "Retrieve outgoing payments", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/controllers.OutgoingInvoice" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.ErrorResponse" + } + } + } + } + }, + "/getuserinvoices": { + "get": { + "security": [ + { + "OAuth2Password": [] + } + ], + "description": "Returns a list of incoming invoices for a user", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Account" + ], + "summary": "Retrieve incoming invoices", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/controllers.IncomingInvoice" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.ErrorResponse" + } + } + } + } + }, + "/invoice/{user_login}": { + "post": { + "description": "Returns a new bolt11 invoice for a user with given login, without an Authorization Header", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Invoice" + ], + "summary": "Generate a new invoice", + "parameters": [ + { + "type": "string", + "description": "User Login", + "name": "user_login", + "in": "path", + "required": true + }, + { + "description": "Add Invoice", + "name": "invoice", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/controllers.AddInvoiceRequestBody" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.AddInvoiceResponseBody" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.ErrorResponse" + } + } + } + } + }, + "/invoices/stream": { + "get": { + "security": [ + { + "OAuth2Password": [] + } + ], + "description": "Websocket: won't work with Swagger web UI. Returns a stream of settled incoming payments.\nA keep-alive message is sent on startup and every 30s.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Invoice" + ], + "summary": "Websocket for incoming payments", + "parameters": [ + { + "type": "string", + "description": "Auth token, retrieved from /auth endpoint", + "name": "token", + "in": "query", + "required": true + }, + { + "type": "string", + "description": "Payment hash of earliest invoice. If specified, missing updates starting from this payment will be sent.", + "name": "since_payment_hash", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/controllers.InvoiceEventWrapper" + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.ErrorResponse" + } + } + } + } + }, + "/keysend": { + "post": { + "security": [ + { + "OAuth2Password": [] + } + ], + "description": "Pay a node without an invoice using it's public key", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Payment" + ], + "summary": "Make a keysend payment", + "parameters": [ + { + "description": "Invoice to pay", + "name": "KeySendRequestBody", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/controllers.KeySendRequestBody" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.KeySendResponseBody" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.ErrorResponse" + } + } + } + } + }, + "/payinvoice": { + "post": { + "security": [ + { + "OAuth2Password": [] + } + ], + "description": "Pay a bolt11 invoice", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Payment" + ], + "summary": "Pay an invoice", + "parameters": [ + { + "description": "Invoice to pay", + "name": "PayInvoiceRequest", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/controllers.PayInvoiceRequestBody" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/controllers.PayInvoiceResponseBody" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/responses.ErrorResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.ErrorResponse" + } + } + } + } } }, "definitions": { + "controllers.AddInvoiceRequestBody": { + "type": "object", + "properties": { + "amt": { + "description": "amount in Satoshi" + }, + "description_hash": { + "type": "string" + }, + "memo": { + "type": "string" + } + } + }, + "controllers.AddInvoiceResponseBody": { + "type": "object", + "properties": { + "pay_req": { + "type": "string" + }, + "payment_request": { + "type": "string" + }, + "r_hash": { + "type": "string" + } + } + }, + "controllers.AuthRequestBody": { + "type": "object", + "properties": { + "login": { + "type": "string" + }, + "password": { + "type": "string" + }, + "refresh_token": { + "type": "string" + } + } + }, + "controllers.AuthResponseBody": { + "type": "object", + "properties": { + "access_token": { + "type": "string" + }, + "refresh_token": { + "type": "string" + } + } + }, "controllers.BalanceResponse": { "type": "object", "properties": { @@ -113,6 +657,27 @@ } } }, + "controllers.Chain": { + "type": "object", + "properties": { + "chain": { + "description": "The blockchain the node is on (eg bitcoin, litecoin)", + "type": "string" + }, + "network": { + "description": "The network the node is on (eg regtest, testnet, mainnet)", + "type": "string" + } + } + }, + "controllers.CheckPaymentResponseBody": { + "type": "object", + "properties": { + "paid": { + "type": "boolean" + } + } + }, "controllers.CreateUserRequestBody": { "type": "object", "properties": { @@ -141,6 +706,281 @@ } } }, + "controllers.Feature": { + "type": "object", + "properties": { + "is_known": { + "type": "boolean" + }, + "is_required": { + "type": "boolean" + }, + "name": { + "type": "string" + } + } + }, + "controllers.GetInfoResponse": { + "type": "object", + "properties": { + "alias": { + "description": "If applicable, the alias of the current node, e.g. \"bob\"", + "type": "string" + }, + "best_header_timestamp": { + "description": "Timestamp of the block best known to the wallet", + "type": "integer" + }, + "block_hash": { + "description": "The node's current view of the hash of the best block", + "type": "string" + }, + "block_height": { + "description": "The node's current view of the height of the best block", + "type": "integer" + }, + "chains": { + "description": "A list of active chains the node is connected to", + "type": "array", + "items": { + "$ref": "#/definitions/controllers.Chain" + } + }, + "color": { + "description": "The color of the current node in hex code format", + "type": "string" + }, + "commit_hash": { + "description": "The SHA1 commit hash that the daemon is compiled with.", + "type": "string" + }, + "features": { + "description": "Features that our node has advertised in our init message, node\nannouncements and invoices.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/controllers.Feature" + } + }, + "identity_pubkey": { + "description": "The identity pubkey of the current node.", + "type": "string" + }, + "num_active_channels": { + "description": "Number of active channels", + "type": "integer" + }, + "num_inactive_channels": { + "description": "Number of inactive channels", + "type": "integer" + }, + "num_peers": { + "description": "Number of peers", + "type": "integer" + }, + "num_pending_channels": { + "description": "Number of pending channels", + "type": "integer" + }, + "synced_to_chain": { + "description": "Whether the wallet's view is synced to the main chain", + "type": "boolean" + }, + "synced_to_graph": { + "description": "Whether we consider ourselves synced with the public channel graph.", + "type": "boolean" + }, + "testnet": { + "description": "Whether the current node is connected to testnet. This field is\ndeprecated and the network field should be used instead\n\nDeprecated: Do not use.", + "type": "boolean" + }, + "uris": { + "description": "The URIs of the current node.", + "type": "array", + "items": { + "type": "string" + } + }, + "version": { + "description": "The version of the LND software that the node is running.", + "type": "string" + } + } + }, + "controllers.IncomingInvoice": { + "type": "object", + "properties": { + "amt": { + "type": "integer" + }, + "description": { + "type": "string" + }, + "expire_time": { + "type": "integer" + }, + "ispaid": { + "type": "boolean" + }, + "pay_req": { + "type": "string" + }, + "payment_hash": {}, + "payment_request": { + "type": "string" + }, + "r_hash": {}, + "timestamp": { + "type": "integer" + }, + "type": { + "type": "string" + } + } + }, + "controllers.InvoiceEventWrapper": { + "type": "object", + "properties": { + "invoice": { + "$ref": "#/definitions/controllers.IncomingInvoice" + }, + "type": { + "type": "string" + } + } + }, + "controllers.KeySendRequestBody": { + "type": "object", + "required": [ + "amount", + "destination" + ], + "properties": { + "amount": { + "type": "integer" + }, + "customRecords": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "destination": { + "type": "string" + }, + "memo": { + "type": "string" + } + } + }, + "controllers.KeySendResponseBody": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "description_hash": { + "type": "string" + }, + "destination": { + "type": "string" + }, + "num_satoshis": { + "type": "integer" + }, + "payment_error": { + "type": "string" + }, + "payment_hash": { + "$ref": "#/definitions/lib.JavaScriptBuffer" + }, + "payment_preimage": { + "$ref": "#/definitions/lib.JavaScriptBuffer" + }, + "payment_route": { + "$ref": "#/definitions/service.Route" + } + } + }, + "controllers.OutgoingInvoice": { + "type": "object", + "properties": { + "fee": { + "type": "integer" + }, + "memo": { + "type": "string" + }, + "payment_hash": {}, + "payment_preimage": { + "type": "string" + }, + "r_hash": {}, + "timestamp": { + "type": "integer" + }, + "type": { + "type": "string" + }, + "value": { + "type": "integer" + } + } + }, + "controllers.PayInvoiceRequestBody": { + "type": "object", + "required": [ + "invoice" + ], + "properties": { + "amount": {}, + "invoice": { + "type": "string" + } + } + }, + "controllers.PayInvoiceResponseBody": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "description_hash": { + "type": "string" + }, + "num_satoshis": { + "type": "integer" + }, + "pay_req": { + "type": "string" + }, + "payment_error": { + "type": "string" + }, + "payment_hash": { + "$ref": "#/definitions/lib.JavaScriptBuffer" + }, + "payment_preimage": { + "$ref": "#/definitions/lib.JavaScriptBuffer" + }, + "payment_request": { + "type": "string" + }, + "payment_route": { + "$ref": "#/definitions/service.Route" + } + } + }, + "lib.JavaScriptBuffer": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "integer" + } + } + } + }, "responses.ErrorResponse": { "type": "object", "properties": { @@ -154,6 +994,17 @@ "type": "string" } } + }, + "service.Route": { + "type": "object", + "properties": { + "total_amt": { + "type": "integer" + }, + "total_fees": { + "type": "integer" + } + } } }, "securityDefinitions": { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index b3ba793..09e278e 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -1,5 +1,39 @@ basePath: / definitions: + controllers.AddInvoiceRequestBody: + properties: + amt: + description: amount in Satoshi + description_hash: + type: string + memo: + type: string + type: object + controllers.AddInvoiceResponseBody: + properties: + pay_req: + type: string + payment_request: + type: string + r_hash: + type: string + type: object + controllers.AuthRequestBody: + properties: + login: + type: string + password: + type: string + refresh_token: + type: string + type: object + controllers.AuthResponseBody: + properties: + access_token: + type: string + refresh_token: + type: string + type: object controllers.BalanceResponse: properties: btc: @@ -8,6 +42,20 @@ definitions: type: integer type: object type: object + controllers.Chain: + properties: + chain: + description: The blockchain the node is on (eg bitcoin, litecoin) + type: string + network: + description: The network the node is on (eg regtest, testnet, mainnet) + type: string + type: object + controllers.CheckPaymentResponseBody: + properties: + paid: + type: boolean + type: object controllers.CreateUserRequestBody: properties: accounttype: @@ -26,6 +74,201 @@ definitions: password: type: string type: object + controllers.Feature: + properties: + is_known: + type: boolean + is_required: + type: boolean + name: + type: string + type: object + controllers.GetInfoResponse: + properties: + alias: + description: If applicable, the alias of the current node, e.g. "bob" + type: string + best_header_timestamp: + description: Timestamp of the block best known to the wallet + type: integer + block_hash: + description: The node's current view of the hash of the best block + type: string + block_height: + description: The node's current view of the height of the best block + type: integer + chains: + description: A list of active chains the node is connected to + items: + $ref: '#/definitions/controllers.Chain' + type: array + color: + description: The color of the current node in hex code format + type: string + commit_hash: + description: The SHA1 commit hash that the daemon is compiled with. + type: string + features: + additionalProperties: + $ref: '#/definitions/controllers.Feature' + description: |- + Features that our node has advertised in our init message, node + announcements and invoices. + type: object + identity_pubkey: + description: The identity pubkey of the current node. + type: string + num_active_channels: + description: Number of active channels + type: integer + num_inactive_channels: + description: Number of inactive channels + type: integer + num_peers: + description: Number of peers + type: integer + num_pending_channels: + description: Number of pending channels + type: integer + synced_to_chain: + description: Whether the wallet's view is synced to the main chain + type: boolean + synced_to_graph: + description: Whether we consider ourselves synced with the public channel + graph. + type: boolean + testnet: + description: |- + Whether the current node is connected to testnet. This field is + deprecated and the network field should be used instead + + Deprecated: Do not use. + type: boolean + uris: + description: The URIs of the current node. + items: + type: string + type: array + version: + description: The version of the LND software that the node is running. + type: string + type: object + controllers.IncomingInvoice: + properties: + amt: + type: integer + description: + type: string + expire_time: + type: integer + ispaid: + type: boolean + pay_req: + type: string + payment_hash: {} + payment_request: + type: string + r_hash: {} + timestamp: + type: integer + type: + type: string + type: object + controllers.InvoiceEventWrapper: + properties: + invoice: + $ref: '#/definitions/controllers.IncomingInvoice' + type: + type: string + type: object + controllers.KeySendRequestBody: + properties: + amount: + type: integer + customRecords: + additionalProperties: + type: string + type: object + destination: + type: string + memo: + type: string + required: + - amount + - destination + type: object + controllers.KeySendResponseBody: + properties: + description: + type: string + description_hash: + type: string + destination: + type: string + num_satoshis: + type: integer + payment_error: + type: string + payment_hash: + $ref: '#/definitions/lib.JavaScriptBuffer' + payment_preimage: + $ref: '#/definitions/lib.JavaScriptBuffer' + payment_route: + $ref: '#/definitions/service.Route' + type: object + controllers.OutgoingInvoice: + properties: + fee: + type: integer + memo: + type: string + payment_hash: {} + payment_preimage: + type: string + r_hash: {} + timestamp: + type: integer + type: + type: string + value: + type: integer + type: object + controllers.PayInvoiceRequestBody: + properties: + amount: {} + invoice: + type: string + required: + - invoice + type: object + controllers.PayInvoiceResponseBody: + properties: + description: + type: string + description_hash: + type: string + num_satoshis: + type: integer + pay_req: + type: string + payment_error: + type: string + payment_hash: + $ref: '#/definitions/lib.JavaScriptBuffer' + payment_preimage: + $ref: '#/definitions/lib.JavaScriptBuffer' + payment_request: + type: string + payment_route: + $ref: '#/definitions/service.Route' + type: object + lib.JavaScriptBuffer: + properties: + data: + items: + type: integer + type: array + type: object responses.ErrorResponse: properties: code: @@ -35,6 +278,13 @@ definitions: message: type: string type: object + service.Route: + properties: + total_amt: + type: integer + total_fees: + type: integer + type: object info: contact: email: hello@getalby.com @@ -48,6 +298,67 @@ info: title: LNDhub.go version: 0.6.1 paths: + /addinvoice: + post: + consumes: + - application/json + description: Returns a new bolt11 invoice + parameters: + - description: Add Invoice + in: body + name: invoice + required: true + schema: + $ref: '#/definitions/controllers.AddInvoiceRequestBody' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/controllers.AddInvoiceResponseBody' + "400": + description: Bad Request + schema: + $ref: '#/definitions/responses.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/responses.ErrorResponse' + security: + - OAuth2Password: [] + summary: Generate a new invoice + tags: + - Invoice + /auth: + post: + consumes: + - application/json + description: Exchanges a login + password for a token + parameters: + - description: Login and password + in: body + name: AuthRequestBody + schema: + $ref: '#/definitions/controllers.AuthRequestBody' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/controllers.AuthResponseBody' + "400": + description: Bad Request + schema: + $ref: '#/definitions/responses.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/responses.ErrorResponse' + summary: Authenticate + tags: + - Account /balance: get: consumes: @@ -72,7 +383,38 @@ paths: - OAuth2Password: [] summary: Retrieve balance tags: - - account + - Account + /checkpayment/{payment_hash}: + get: + consumes: + - application/json + description: Checks if an invoice is paid, can be incoming our outgoing + parameters: + - description: Payment hash + in: path + name: payment_hash + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/controllers.CheckPaymentResponseBody' + "400": + description: Bad Request + schema: + $ref: '#/definitions/responses.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/responses.ErrorResponse' + security: + - OAuth2Password: [] + summary: Check if an invoice is paid + tags: + - Invoice /create: post: consumes: @@ -100,6 +442,230 @@ paths: schema: $ref: '#/definitions/responses.ErrorResponse' summary: Create an account + tags: + - Account + /getinfo: + get: + consumes: + - application/json + description: Returns info about the backend node powering this LNDhub instance + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/controllers.GetInfoResponse' + "400": + description: Bad Request + schema: + $ref: '#/definitions/responses.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/responses.ErrorResponse' + security: + - OAuth2Password: [] + summary: Get info about the Lightning node + tags: + - Info + /gettxs: + get: + consumes: + - application/json + description: Returns a list of outgoing payments for a user + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/controllers.OutgoingInvoice' + type: array + "400": + description: Bad Request + schema: + $ref: '#/definitions/responses.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/responses.ErrorResponse' + security: + - OAuth2Password: [] + summary: Retrieve outgoing payments + tags: + - Account + /getuserinvoices: + get: + consumes: + - application/json + description: Returns a list of incoming invoices for a user + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/controllers.IncomingInvoice' + type: array + "400": + description: Bad Request + schema: + $ref: '#/definitions/responses.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/responses.ErrorResponse' + security: + - OAuth2Password: [] + summary: Retrieve incoming invoices + tags: + - Account + /invoice/{user_login}: + post: + consumes: + - application/json + description: Returns a new bolt11 invoice for a user with given login, without + an Authorization Header + parameters: + - description: User Login + in: path + name: user_login + required: true + type: string + - description: Add Invoice + in: body + name: invoice + required: true + schema: + $ref: '#/definitions/controllers.AddInvoiceRequestBody' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/controllers.AddInvoiceResponseBody' + "400": + description: Bad Request + schema: + $ref: '#/definitions/responses.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/responses.ErrorResponse' + summary: Generate a new invoice + tags: + - Invoice + /invoices/stream: + get: + consumes: + - application/json + description: |- + Websocket: won't work with Swagger web UI. Returns a stream of settled incoming payments. + A keep-alive message is sent on startup and every 30s. + parameters: + - description: Auth token, retrieved from /auth endpoint + in: query + name: token + required: true + type: string + - description: Payment hash of earliest invoice. If specified, missing updates + starting from this payment will be sent. + in: query + name: since_payment_hash + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/controllers.InvoiceEventWrapper' + type: array + "400": + description: Bad Request + schema: + $ref: '#/definitions/responses.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/responses.ErrorResponse' + security: + - OAuth2Password: [] + summary: Websocket for incoming payments + tags: + - Invoice + /keysend: + post: + consumes: + - application/json + description: Pay a node without an invoice using it's public key + parameters: + - description: Invoice to pay + in: body + name: KeySendRequestBody + required: true + schema: + $ref: '#/definitions/controllers.KeySendRequestBody' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/controllers.KeySendResponseBody' + "400": + description: Bad Request + schema: + $ref: '#/definitions/responses.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/responses.ErrorResponse' + security: + - OAuth2Password: [] + summary: Make a keysend payment + tags: + - Payment + /payinvoice: + post: + consumes: + - application/json + description: Pay a bolt11 invoice + parameters: + - description: Invoice to pay + in: body + name: PayInvoiceRequest + required: true + schema: + $ref: '#/definitions/controllers.PayInvoiceRequestBody' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/controllers.PayInvoiceResponseBody' + "400": + description: Bad Request + schema: + $ref: '#/definitions/responses.ErrorResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/responses.ErrorResponse' + security: + - OAuth2Password: [] + summary: Pay an invoice + tags: + - Payment +schemes: +- http +- https securityDefinitions: OAuth2Password: flow: password diff --git a/main.go b/main.go index 252f496..d02fee1 100644 --- a/main.go +++ b/main.go @@ -42,9 +42,9 @@ var indexHtml string //go:embed static/* var staticContent embed.FS -// @title LNDhub.go -// @version 0.6.1 -// @description Accounting wrapper for the Lightning Network providing separate accounts for end-users. +// @title LNDhub.go +// @version 0.6.1 +// @description Accounting wrapper for the Lightning Network providing separate accounts for end-users. // @contact.name Alby // @contact.url https://getalby.com @@ -57,6 +57,7 @@ var staticContent embed.FS // @securitydefinitions.oauth2.password OAuth2Password // @tokenUrl /auth +// @schemes http https func main() { c := &service.Config{} From 2fc18ae492cac9a323f1d0c7c91a02e7a68572d3 Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Tue, 26 Apr 2022 13:55:08 +0200 Subject: [PATCH 05/14] make host configurable --- lib/service/config.go | 1 + main.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/service/config.go b/lib/service/config.go index 61e5f80..576b22b 100644 --- a/lib/service/config.go +++ b/lib/service/config.go @@ -11,6 +11,7 @@ type Config struct { LNDMacaroonHex string `envconfig:"LND_MACAROON_HEX" required:"true"` LNDCertHex string `envconfig:"LND_CERT_HEX"` CustomName string `envconfig:"CUSTOM_NAME"` + Host string `envconfig:"HOST" default:"localhost:3000"` Port int `envconfig:"PORT" default:"3000"` DefaultRateLimit int `envconfig:"DEFAULT_RATE_LIMIT" default:"10"` StrictRateLimit int `envconfig:"STRICT_RATE_LIMIT" default:"10"` diff --git a/main.go b/main.go index d02fee1..56b2f6a 100644 --- a/main.go +++ b/main.go @@ -184,7 +184,7 @@ func main() { e.GET("/invoices/stream", controllers.NewInvoiceStreamController(svc).StreamInvoices) //Swagger API spec - docs.SwaggerInfo.Host = "localhost:3000" + docs.SwaggerInfo.Host = c.Host e.GET("/swagger/*", echoSwagger.WrapHandler) // Subscribe to LND invoice updates in the background From 2744ec73d0cc172fc0894550b2d1807c0935628a Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Wed, 27 Apr 2022 09:53:26 +0200 Subject: [PATCH 06/14] scheme: make https default --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index 56b2f6a..7c91961 100644 --- a/main.go +++ b/main.go @@ -57,7 +57,7 @@ var staticContent embed.FS // @securitydefinitions.oauth2.password OAuth2Password // @tokenUrl /auth -// @schemes http https +// @schemes https http func main() { c := &service.Config{} From 887e5fbf89d8742b8a5c5bf42727e0c2e04d3888 Mon Sep 17 00:00:00 2001 From: Michael Bumann Date: Mon, 2 May 2022 09:38:57 +0200 Subject: [PATCH 07/14] Log user ids on more errors This should help analyzing the logs better for a specific user. --- controllers/addinvoice.ctrl.go | 4 ++-- controllers/checkpayment.ctrl.go | 6 +++--- controllers/keysend.ctrl.go | 4 ++-- controllers/payinvoice.ctrl.go | 10 +++++----- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/controllers/addinvoice.ctrl.go b/controllers/addinvoice.ctrl.go index cd8d9a1..d191ab2 100644 --- a/controllers/addinvoice.ctrl.go +++ b/controllers/addinvoice.ctrl.go @@ -64,11 +64,11 @@ func AddInvoice(c echo.Context, svc *service.LndhubService, userID int64) error if err != nil { return c.JSON(http.StatusBadRequest, responses.BadArgumentsError) } - c.Logger().Infof("Adding invoice: user_id=%v memo=%s value=%v description_hash=%s", userID, body.Memo, amount, body.DescriptionHash) + c.Logger().Infof("Adding invoice: user_id:%v memo:%s value:%v description_hash:%s", userID, body.Memo, amount, body.DescriptionHash) invoice, err := svc.AddIncomingInvoice(c.Request().Context(), userID, amount, body.Memo, body.DescriptionHash) if err != nil { - c.Logger().Errorf("Error creating invoice: %v", err) + c.Logger().Errorf("Error creating invoice: user_id:%v error: %v", userID, err) sentry.CaptureException(err) return c.JSON(http.StatusBadRequest, responses.BadArgumentsError) } diff --git a/controllers/checkpayment.ctrl.go b/controllers/checkpayment.ctrl.go index 3c17c02..64b3974 100644 --- a/controllers/checkpayment.ctrl.go +++ b/controllers/checkpayment.ctrl.go @@ -34,14 +34,14 @@ func NewCheckPaymentController(svc *service.LndhubService) *CheckPaymentControll // @Router /checkpayment/{payment_hash} [get] // @Security OAuth2Password func (controller *CheckPaymentController) CheckPayment(c echo.Context) error { - userId := c.Get("UserID").(int64) + userID := c.Get("UserID").(int64) rHash := c.Param("payment_hash") - invoice, err := controller.svc.FindInvoiceByPaymentHash(c.Request().Context(), userId, rHash) + invoice, err := controller.svc.FindInvoiceByPaymentHash(c.Request().Context(), userID, rHash) // Probably we did not find the invoice if err != nil { - c.Logger().Errorf("Invalid checkpayment request payment_hash=%s", rHash) + c.Logger().Errorf("Invalid checkpayment request user_id:%v payment_hash:%s", userID, rHash) return c.JSON(http.StatusBadRequest, responses.BadArgumentsError) } diff --git a/controllers/keysend.ctrl.go b/controllers/keysend.ctrl.go index abeaa57..b197fe1 100644 --- a/controllers/keysend.ctrl.go +++ b/controllers/keysend.ctrl.go @@ -86,7 +86,7 @@ func (controller *KeySendController) KeySend(c echo.Context) error { } if currentBalance < invoice.Amount { - c.Logger().Errorf("User does not have enough balance invoice_id=%v user_id=%v balance=%v amount=%v", invoice.ID, userID, currentBalance, invoice.Amount) + c.Logger().Errorf("User does not have enough balance invoice_id:%v user_id:%v balance:%v amount:%v", invoice.ID, userID, currentBalance, invoice.Amount) return c.JSON(http.StatusBadRequest, responses.NotEnoughBalanceError) } @@ -100,7 +100,7 @@ func (controller *KeySendController) KeySend(c echo.Context) error { } sendPaymentResponse, err := controller.svc.PayInvoice(c.Request().Context(), invoice) if err != nil { - c.Logger().Errorf("Payment failed: %v", err) + c.Logger().Errorf("Payment failed: user_id:%v error: %v", userID, err) sentry.CaptureException(err) return c.JSON(http.StatusBadRequest, echo.Map{ "error": true, diff --git a/controllers/payinvoice.ctrl.go b/controllers/payinvoice.ctrl.go index de2c36e..0bf788c 100644 --- a/controllers/payinvoice.ctrl.go +++ b/controllers/payinvoice.ctrl.go @@ -54,12 +54,12 @@ func (controller *PayInvoiceController) PayInvoice(c echo.Context) error { userID := c.Get("UserID").(int64) reqBody := PayInvoiceRequestBody{} if err := c.Bind(&reqBody); err != nil { - c.Logger().Errorf("Failed to load payinvoice request body: %v", err) + c.Logger().Errorf("Failed to load payinvoice request body: user_id:%v error: %v", userID, err) return c.JSON(http.StatusBadRequest, responses.BadArgumentsError) } if err := c.Validate(&reqBody); err != nil { - c.Logger().Errorf("Invalid payinvoice request body: %v", err) + c.Logger().Errorf("Invalid payinvoice request body user_id:%v error: %v", userID, err) return c.JSON(http.StatusBadRequest, responses.BadArgumentsError) } @@ -67,7 +67,7 @@ func (controller *PayInvoiceController) PayInvoice(c echo.Context) error { paymentRequest = strings.ToLower(paymentRequest) decodedPaymentRequest, err := controller.svc.DecodePaymentRequest(c.Request().Context(), paymentRequest) if err != nil { - c.Logger().Errorf("Invalid payment request: %v", err) + c.Logger().Errorf("Invalid payment request user_id:%v error: %v", userID, err) sentry.CaptureException(err) return c.JSON(http.StatusBadRequest, responses.BadArgumentsError) } @@ -99,14 +99,14 @@ func (controller *PayInvoiceController) PayInvoice(c echo.Context) error { } if currentBalance < invoice.Amount { - c.Logger().Errorf("User does not have enough balance invoice_id=%v user_id=%v balance=%v amount=%v", invoice.ID, userID, currentBalance, invoice.Amount) + c.Logger().Errorf("User does not have enough balance invoice_id:%v user_id:%v balance:%v amount:%v", invoice.ID, userID, currentBalance, invoice.Amount) return c.JSON(http.StatusBadRequest, responses.NotEnoughBalanceError) } sendPaymentResponse, err := controller.svc.PayInvoice(c.Request().Context(), invoice) if err != nil { - c.Logger().Errorf("Payment failed: %v", err) + c.Logger().Errorf("Payment failed invoice_id:%v user_id:%v error: %v", invoice.ID, userID, err) sentry.CaptureException(err) return c.JSON(http.StatusBadRequest, echo.Map{ "error": true, From 78a5f6e3665d9fdecfca7c787731af91abc352ad Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Mon, 2 May 2022 11:29:24 +0200 Subject: [PATCH 08/14] integration tests: reproduce 0 amt invoice bug --- integration_tests/incoming_payment_test.go | 39 ++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/integration_tests/incoming_payment_test.go b/integration_tests/incoming_payment_test.go index 91e9b2e..51a45e1 100644 --- a/integration_tests/incoming_payment_test.go +++ b/integration_tests/incoming_payment_test.go @@ -110,6 +110,45 @@ func (suite *IncomingPaymentTestSuite) TestIncomingPayment() { assert.Equal(suite.T(), int64(fundingSatAmt), balance.BTC.AvailableBalance) } +func (suite *IncomingPaymentTestSuite) TestIncomingPaymentZeroAmt() { + var buf bytes.Buffer + req := httptest.NewRequest(http.MethodGet, "/balance", &buf) + req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", suite.userToken)) + rec := httptest.NewRecorder() + suite.echo.Use(tokens.Middleware([]byte(suite.service.Config.JWTSecret))) + suite.echo.GET("/balance", controllers.NewBalanceController(suite.service).Balance) + suite.echo.POST("/addinvoice", controllers.NewAddInvoiceController(suite.service).AddInvoice) + suite.echo.ServeHTTP(rec, req) + //lookup balance before + balance := &ExpectedBalanceResponse{} + assert.Equal(suite.T(), http.StatusOK, rec.Code) + assert.NoError(suite.T(), json.NewDecoder(rec.Body).Decode(&balance)) + initialBalance := balance.BTC.AvailableBalance + fundingSatAmt := 0 + sendSatAmt := 10 + invoiceResponse := suite.createAddInvoiceReq(fundingSatAmt, "integration test IncomingPaymentTestSuite", suite.userToken) + //try to pay invoice with external node + // Prepare the LNRPC call + sendPaymentRequest := lnrpc.SendRequest{ + PaymentRequest: invoiceResponse.PayReq, + Amt: int64(sendSatAmt), + FeeLimit: nil, + } + _, err := suite.fundingClient.SendPaymentSync(context.Background(), &sendPaymentRequest) + assert.NoError(suite.T(), err) + + //wait a bit for the callback event to hit + time.Sleep(100 * time.Millisecond) + //check balance again + req = httptest.NewRequest(http.MethodGet, "/balance", &buf) + req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", suite.userToken)) + suite.echo.ServeHTTP(rec, req) + balance = &ExpectedBalanceResponse{} + assert.Equal(suite.T(), http.StatusOK, rec.Code) + assert.NoError(suite.T(), json.NewDecoder(rec.Body).Decode(&balance)) + //assert the payment value was added to the user's account + assert.Equal(suite.T(), initialBalance+int64(sendSatAmt), balance.BTC.AvailableBalance) +} func TestIncomingPaymentTestSuite(t *testing.T) { suite.Run(t, new(IncomingPaymentTestSuite)) From f99409d14536f249eac9608ef11266e2b510ce93 Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Mon, 2 May 2022 11:30:17 +0200 Subject: [PATCH 09/14] invoice: use rawinvoice amt paid --- lib/service/invoicesubscription.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/service/invoicesubscription.go b/lib/service/invoicesubscription.go index 580f900..f17b7a6 100644 --- a/lib/service/invoicesubscription.go +++ b/lib/service/invoicesubscription.go @@ -81,7 +81,11 @@ func (svc *LndhubService) ProcessInvoiceUpdate(ctx context.Context, rawInvoice * InvoiceID: invoice.ID, CreditAccountID: creditAccount.ID, DebitAccountID: debitAccount.ID, - Amount: invoice.Amount, + Amount: rawInvoice.AmtPaidSat, + } + + if rawInvoice.AmtPaidSat != invoice.Amount { + svc.Logger.Infof("Incoming invoice amount mismatch. Invoice id:%v, amt: %d, amt_paid: %d.", invoice.ID, invoice.Amount, rawInvoice.AmtPaidSat) } // Save the transaction entry From cd72e81b246811addfeaccb047964ae60254e089 Mon Sep 17 00:00:00 2001 From: kiwiidb <33457577+kiwiidb@users.noreply.github.com> Date: Mon, 2 May 2022 12:39:42 +0200 Subject: [PATCH 10/14] Update lib/service/invoicesubscription.go Add user ID Co-authored-by: Michael Bumann --- lib/service/invoicesubscription.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/service/invoicesubscription.go b/lib/service/invoicesubscription.go index f17b7a6..378d746 100644 --- a/lib/service/invoicesubscription.go +++ b/lib/service/invoicesubscription.go @@ -85,7 +85,7 @@ func (svc *LndhubService) ProcessInvoiceUpdate(ctx context.Context, rawInvoice * } if rawInvoice.AmtPaidSat != invoice.Amount { - svc.Logger.Infof("Incoming invoice amount mismatch. Invoice id:%v, amt: %d, amt_paid: %d.", invoice.ID, invoice.Amount, rawInvoice.AmtPaidSat) + svc.Logger.Infof("Incoming invoice amount mismatch. user_id:%v invoice_id:%v, amt:%d, amt_paid:%d.", invoice.UserID, invoice.ID, invoice.Amount, rawInvoice.AmtPaidSat) } // Save the transaction entry From 3881ab7e785cf2687a5096d63fb2a0d930bc0034 Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Mon, 2 May 2022 14:27:48 +0200 Subject: [PATCH 11/14] add 0 amount outgoing invoice --- controllers/payinvoice.ctrl.go | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/controllers/payinvoice.ctrl.go b/controllers/payinvoice.ctrl.go index 0bf788c..245dd35 100644 --- a/controllers/payinvoice.ctrl.go +++ b/controllers/payinvoice.ctrl.go @@ -71,22 +71,18 @@ func (controller *PayInvoiceController) PayInvoice(c echo.Context) error { sentry.CaptureException(err) return c.JSON(http.StatusBadRequest, responses.BadArgumentsError) } - // TODO: zero amount invoices - /* - _, err = controller.svc.ParseInt(reqBody.Amount) - if err != nil { - return c.JSON(http.StatusBadRequest, echo.Map{ - "error": true, - "code": 8, - "message": "Bad arguments", - }) - } - */ lnPayReq := &lnd.LNPayReq{ PayReq: decodedPaymentRequest, Keysend: false, } + if decodedPaymentRequest.NumSatoshis == 0 { + amt, err := controller.svc.ParseInt(reqBody.Amount) + if err != nil || amt <= 0 { + return c.JSON(http.StatusBadRequest, responses.BadArgumentsError) + } + lnPayReq.PayReq.NumSatoshis = amt + } invoice, err := controller.svc.AddOutgoingInvoice(c.Request().Context(), userID, paymentRequest, lnPayReq) if err != nil { From e15e55c1753dd91783ab7e946cab655248c3ea9e Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Mon, 2 May 2022 19:10:39 +0200 Subject: [PATCH 12/14] refactor tests --- integration_tests/checkpayment_test.go | 4 ++- integration_tests/gettxs_test.go | 4 ++- integration_tests/internal_payment_test.go | 8 +++-- integration_tests/outgoing_payment_test.go | 38 ++++++++++++++++++++-- integration_tests/util.go | 6 ++-- 5 files changed, 50 insertions(+), 10 deletions(-) diff --git a/integration_tests/checkpayment_test.go b/integration_tests/checkpayment_test.go index 4b0b9a7..a0d8051 100644 --- a/integration_tests/checkpayment_test.go +++ b/integration_tests/checkpayment_test.go @@ -109,7 +109,9 @@ func (suite *CheckPaymentTestSuite) TestCheckPaymentProperIsPaidResponse() { assert.False(suite.T(), checkPaymentResponse.IsPaid) // pay external from user - payResponse := suite.createPayInvoiceReq(invoice.PaymentRequest, suite.userToken) + payResponse := suite.createPayInvoiceReq(&ExpectedPayInvoiceRequestBody{ + Invoice: invoice.PayReq, + }, suite.userToken) assert.NotEmpty(suite.T(), payResponse.PaymentPreimage) // check payment is paid diff --git a/integration_tests/gettxs_test.go b/integration_tests/gettxs_test.go index c1d4259..ead6a8b 100644 --- a/integration_tests/gettxs_test.go +++ b/integration_tests/gettxs_test.go @@ -102,7 +102,9 @@ func (suite *GetTxTestSuite) TestGetOutgoingInvoices() { // create invoice invoice = suite.createAddInvoiceReq(500, "integration test internal payment alice", suite.userToken) // pay invoice, this will create outgoing invoice and settle it - suite.createPayInvoiceReq(invoice.PayReq, suite.userToken) + suite.createPayInvoiceReq(&ExpectedPayInvoiceRequestBody{ + Invoice: invoice.PayReq, + }, suite.userToken) // check invoices again req = httptest.NewRequest(http.MethodGet, "/gettxs", nil) req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", suite.userToken)) diff --git a/integration_tests/internal_payment_test.go b/integration_tests/internal_payment_test.go index 9c91a90..b721615 100644 --- a/integration_tests/internal_payment_test.go +++ b/integration_tests/internal_payment_test.go @@ -103,7 +103,9 @@ func (suite *PaymentTestSuite) TestInternalPayment() { //create invoice for bob bobInvoice := suite.createAddInvoiceReq(bobSatRequested, "integration test internal payment bob", suite.bobToken) //pay bob from alice - payResponse := suite.createPayInvoiceReq(bobInvoice.PayReq, suite.aliceToken) + payResponse := suite.createPayInvoiceReq(&ExpectedPayInvoiceRequestBody{ + Invoice: bobInvoice.PayReq, + }, suite.aliceToken) assert.NotEmpty(suite.T(), payResponse.PaymentPreimage) aliceId := getUserIdFromToken(suite.aliceToken) @@ -152,7 +154,9 @@ func (suite *PaymentTestSuite) TestInternalPaymentFail() { //create invoice for bob bobInvoice := suite.createAddInvoiceReq(bobSatRequested, "integration test internal payment bob", suite.bobToken) //pay bob from alice - payResponse := suite.createPayInvoiceReq(bobInvoice.PayReq, suite.aliceToken) + payResponse := suite.createPayInvoiceReq(&ExpectedPayInvoiceRequestBody{ + Invoice: bobInvoice.PayReq, + }, suite.aliceToken) assert.NotEmpty(suite.T(), payResponse.PaymentPreimage) //try to pay same invoice again for make it fail _ = suite.createPayInvoiceReqError(bobInvoice.PayReq, suite.aliceToken) diff --git a/integration_tests/outgoing_payment_test.go b/integration_tests/outgoing_payment_test.go index 5833da8..3968c27 100644 --- a/integration_tests/outgoing_payment_test.go +++ b/integration_tests/outgoing_payment_test.go @@ -38,7 +38,9 @@ func (suite *PaymentTestSuite) TestOutGoingPayment() { invoice, err := suite.fundingClient.AddInvoice(context.Background(), &externalInvoice) assert.NoError(suite.T(), err) //pay external from alice - payResponse := suite.createPayInvoiceReq(invoice.PaymentRequest, suite.aliceToken) + payResponse := suite.createPayInvoiceReq(&ExpectedPayInvoiceRequestBody{ + Invoice: invoice.PaymentRequest, + }, suite.aliceToken) assert.NotEmpty(suite.T(), payResponse.PaymentPreimage) // check that balance was reduced @@ -125,7 +127,9 @@ func (suite *PaymentTestSuite) TestOutGoingPaymentWithNegativeBalance() { invoice, err := suite.fundingClient.AddInvoice(context.Background(), &externalInvoice) assert.NoError(suite.T(), err) //pay external from alice - payResponse := suite.createPayInvoiceReq(invoice.PaymentRequest, suite.aliceToken) + payResponse := suite.createPayInvoiceReq(&ExpectedPayInvoiceRequestBody{ + Invoice: invoice.PaymentRequest, + }, suite.aliceToken) assert.NotEmpty(suite.T(), payResponse.PaymentPreimage) // check that balance was reduced @@ -176,3 +180,33 @@ func (suite *PaymentTestSuite) TestOutGoingPaymentWithNegativeBalance() { // make sure fee entry parent id is previous entry assert.Equal(suite.T(), transactonEntries[1].ID, transactonEntries[2].ParentID) } + +func (suite *PaymentTestSuite) TestZeroAmountInvoice() { + aliceFundingSats := 1000 + amtToPay := 1000 + //fund alice account + invoiceResponse := suite.createAddInvoiceReq(aliceFundingSats, "integration test zero amount payment alice", suite.aliceToken) + sendPaymentRequest := lnrpc.SendRequest{ + PaymentRequest: invoiceResponse.PayReq, + FeeLimit: nil, + } + _, err := suite.fundingClient.SendPaymentSync(context.Background(), &sendPaymentRequest) + assert.NoError(suite.T(), err) + + //wait a bit for the callback event to hit + time.Sleep(100 * time.Millisecond) + + //create external invoice + externalInvoice := lnrpc.Invoice{ + Memo: "integration tests: external pay from alice", + Value: 0, + } + invoice, err := suite.fundingClient.AddInvoice(context.Background(), &externalInvoice) + assert.NoError(suite.T(), err) + //pay external from alice + payResponse := suite.createPayInvoiceReq(&ExpectedPayInvoiceRequestBody{ + Invoice: invoice.PaymentRequest, + Amount: amtToPay, + }, suite.aliceToken) + assert.NotEmpty(suite.T(), payResponse.PaymentPreimage) +} diff --git a/integration_tests/util.go b/integration_tests/util.go index 33c9dc3..b28b835 100644 --- a/integration_tests/util.go +++ b/integration_tests/util.go @@ -229,12 +229,10 @@ func (suite *TestSuite) createKeySendReqError(amount int64, memo, destination, t return checkErrResponse(suite, rec) } -func (suite *TestSuite) createPayInvoiceReq(payReq string, token string) *ExpectedPayInvoiceResponseBody { +func (suite *TestSuite) createPayInvoiceReq(payReq *ExpectedPayInvoiceRequestBody, token string) *ExpectedPayInvoiceResponseBody { rec := httptest.NewRecorder() var buf bytes.Buffer - assert.NoError(suite.T(), json.NewEncoder(&buf).Encode(&ExpectedPayInvoiceRequestBody{ - Invoice: payReq, - })) + assert.NoError(suite.T(), json.NewEncoder(&buf).Encode(payReq)) req := httptest.NewRequest(http.MethodPost, "/payinvoice", &buf) req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON) req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token)) From 733ca4cf92fa6a77760eb21a33e02e5dfd7d8c72 Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Mon, 2 May 2022 19:12:31 +0200 Subject: [PATCH 13/14] add 0 amount test --- integration_tests/outgoing_payment_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/integration_tests/outgoing_payment_test.go b/integration_tests/outgoing_payment_test.go index 3968c27..04f61e7 100644 --- a/integration_tests/outgoing_payment_test.go +++ b/integration_tests/outgoing_payment_test.go @@ -198,7 +198,7 @@ func (suite *PaymentTestSuite) TestZeroAmountInvoice() { //create external invoice externalInvoice := lnrpc.Invoice{ - Memo: "integration tests: external pay from alice", + Memo: "integration tests: zero amount pay from alice", Value: 0, } invoice, err := suite.fundingClient.AddInvoice(context.Background(), &externalInvoice) @@ -209,4 +209,5 @@ func (suite *PaymentTestSuite) TestZeroAmountInvoice() { Amount: amtToPay, }, suite.aliceToken) assert.NotEmpty(suite.T(), payResponse.PaymentPreimage) + assert.Equal(suite.T(), amtToPay, payResponse.Amount) } From f2acb462e2085cde6ce26a06dfc4d464ab0c9261 Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Mon, 2 May 2022 19:23:29 +0200 Subject: [PATCH 14/14] fix test --- integration_tests/outgoing_payment_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration_tests/outgoing_payment_test.go b/integration_tests/outgoing_payment_test.go index 04f61e7..bbc98c8 100644 --- a/integration_tests/outgoing_payment_test.go +++ b/integration_tests/outgoing_payment_test.go @@ -209,5 +209,5 @@ func (suite *PaymentTestSuite) TestZeroAmountInvoice() { Amount: amtToPay, }, suite.aliceToken) assert.NotEmpty(suite.T(), payResponse.PaymentPreimage) - assert.Equal(suite.T(), amtToPay, payResponse.Amount) + assert.Equal(suite.T(), int64(amtToPay), payResponse.Amount) }