diff --git a/controllers/addinvoice.ctrl.go b/controllers/addinvoice.ctrl.go index 04a7b01..d15b19b 100644 --- a/controllers/addinvoice.ctrl.go +++ b/controllers/addinvoice.ctrl.go @@ -33,6 +33,10 @@ type AddInvoiceResponseBody struct { // AddInvoice : Add invoice Controller func (controller *AddInvoiceController) AddInvoice(c echo.Context) error { userID := c.Get("UserID").(int64) + return AddInvoice(c, controller.svc, userID) +} + +func AddInvoice(c echo.Context, svc *service.LndhubService, userID int64) error { var body AddInvoiceRequestBody if err := c.Bind(&body); err != nil { @@ -45,13 +49,13 @@ func (controller *AddInvoiceController) AddInvoice(c echo.Context) error { return c.JSON(http.StatusBadRequest, responses.BadArgumentsError) } - amount, err := controller.svc.ParseInt(body.Amount) + amount, err := svc.ParseInt(body.Amount) 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) - invoice, err := controller.svc.AddIncomingInvoice(c.Request().Context(), userID, amount, body.Memo, 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) sentry.CaptureException(err) diff --git a/controllers/invoice.ctrl.go b/controllers/invoice.ctrl.go new file mode 100644 index 0000000..f0a720b --- /dev/null +++ b/controllers/invoice.ctrl.go @@ -0,0 +1,29 @@ +package controllers + +import ( + "net/http" + + "github.com/getAlby/lndhub.go/lib/responses" + "github.com/getAlby/lndhub.go/lib/service" + "github.com/labstack/echo/v4" +) + +// InvoiceController : Add invoice controller struct +type InvoiceController struct { + svc *service.LndhubService +} + +func NewInvoiceController(svc *service.LndhubService) *InvoiceController { + return &InvoiceController{svc: svc} +} + +// Invoice : Invoice Controller +func (controller *InvoiceController) Invoice(c echo.Context) error { + user, err := controller.svc.FindUserByLogin(c.Request().Context(), c.Param("user_login")) + if err != nil { + c.Logger().Errorf("Failed to find user by login: login %v error %v", c.Param("user_login"), err) + return c.JSON(http.StatusBadRequest, responses.BadArgumentsError) + } + + return AddInvoice(c, controller.svc, user.ID) +} diff --git a/integration_tests/invoice_test.go b/integration_tests/invoice_test.go new file mode 100644 index 0000000..7513def --- /dev/null +++ b/integration_tests/invoice_test.go @@ -0,0 +1,69 @@ +package integration_tests + +import ( + "context" + "log" + "testing" + + "github.com/getAlby/lndhub.go/common" + "github.com/getAlby/lndhub.go/controllers" + "github.com/getAlby/lndhub.go/lib" + "github.com/getAlby/lndhub.go/lib/responses" + "github.com/getAlby/lndhub.go/lib/service" + "github.com/go-playground/validator/v10" + "github.com/labstack/echo/v4" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" +) + +type InvoiceTestSuite struct { + TestSuite + service *service.LndhubService + aliceLogin controllers.CreateUserResponseBody +} + +func (suite *InvoiceTestSuite) SetupSuite() { + svc, err := LndHubTestServiceInit(nil) + if err != nil { + log.Fatalf("Error initializing test service: %v", err) + } + suite.service = svc + users, userTokens, err := createUsers(svc, 1) + if err != nil { + log.Fatalf("Error creating test users: %v", err) + } + e := echo.New() + + e.HTTPErrorHandler = responses.HTTPErrorHandler + e.Validator = &lib.CustomValidator{Validator: validator.New()} + suite.echo = e + assert.Equal(suite.T(), 1, len(users)) + assert.Equal(suite.T(), 1, len(userTokens)) + suite.aliceLogin = users[0] + suite.echo.POST("/invoice/:user_login", controllers.NewInvoiceController(svc).Invoice) +} + +func (suite *InvoiceTestSuite) TearDownTest() { + clearTable(suite.service, "invoices") +} + +func (suite *InvoiceTestSuite) TestAddInvoiceWithoutToken() { + user, _ := suite.service.FindUserByLogin(context.Background(), suite.aliceLogin.Login) + invoicesBefore, _ := suite.service.InvoicesFor(context.Background(), user.ID, common.InvoiceTypeIncoming) + assert.Equal(suite.T(), 0, len(invoicesBefore)) + + suite.createInvoiceReq(10, "test invoice without token", suite.aliceLogin.Login) + + // check if invoice is added + invoicesAfter, _ := suite.service.InvoicesFor(context.Background(), user.ID, common.InvoiceTypeIncoming) + assert.Equal(suite.T(), 1, len(invoicesAfter)) +} + +func (suite *InvoiceTestSuite) TestAddInvoiceForNonExistingUser() { + nonExistingLogin := suite.aliceLogin.Login + "abc" + suite.createInvoiceReqError(10, "test invoice without token", nonExistingLogin) +} + +func TestInvoiceSuite(t *testing.T) { + suite.Run(t, new(InvoiceTestSuite)) +} diff --git a/integration_tests/util.go b/integration_tests/util.go index 06f009b..c4c57e8 100644 --- a/integration_tests/util.go +++ b/integration_tests/util.go @@ -140,12 +140,19 @@ type TestSuite struct { echo *echo.Echo } +func checkErrResponse(suite *TestSuite, rec *httptest.ResponseRecorder) *responses.ErrorResponse { + errorResponse := &responses.ErrorResponse{} + assert.Equal(suite.T(), http.StatusBadRequest, rec.Code) + assert.NoError(suite.T(), json.NewDecoder(rec.Body).Decode(errorResponse)) + return errorResponse +} + func (suite *TestSuite) createAddInvoiceReq(amt int, memo, token string) *controllers.AddInvoiceResponseBody { rec := httptest.NewRecorder() var buf bytes.Buffer assert.NoError(suite.T(), json.NewEncoder(&buf).Encode(&controllers.AddInvoiceRequestBody{ Amount: amt, - Memo: "integration test IncomingPaymentTestSuite", + Memo: memo, })) req := httptest.NewRequest(http.MethodPost, "/addinvoice", &buf) req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON) @@ -157,6 +164,35 @@ func (suite *TestSuite) createAddInvoiceReq(amt int, memo, token string) *contro return invoiceResponse } +func (suite *TestSuite) createInvoiceReq(amt int, memo, userLogin string) *controllers.AddInvoiceResponseBody { + rec := httptest.NewRecorder() + var buf bytes.Buffer + assert.NoError(suite.T(), json.NewEncoder(&buf).Encode(&controllers.AddInvoiceRequestBody{ + Amount: amt, + Memo: memo, + })) + req := httptest.NewRequest(http.MethodPost, "/invoice/"+userLogin, &buf) + req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON) + suite.echo.ServeHTTP(rec, req) + invoiceResponse := &controllers.AddInvoiceResponseBody{} + assert.Equal(suite.T(), http.StatusOK, rec.Code) + assert.NoError(suite.T(), json.NewDecoder(rec.Body).Decode(invoiceResponse)) + return invoiceResponse +} + +func (suite *TestSuite) createInvoiceReqError(amt int, memo, userLogin string) *responses.ErrorResponse { + rec := httptest.NewRecorder() + var buf bytes.Buffer + assert.NoError(suite.T(), json.NewEncoder(&buf).Encode(&controllers.AddInvoiceRequestBody{ + Amount: amt, + Memo: memo, + })) + req := httptest.NewRequest(http.MethodPost, "/invoice/"+userLogin, &buf) + req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON) + suite.echo.ServeHTTP(rec, req) + return checkErrResponse(suite, rec) +} + func (suite *TestSuite) createKeySendReq(amount int64, memo, destination, token string) *controllers.KeySendResponseBody { rec := httptest.NewRecorder() var buf bytes.Buffer @@ -190,11 +226,7 @@ func (suite *TestSuite) createKeySendReqError(amount int64, memo, destination, t req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON) req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token)) suite.echo.ServeHTTP(rec, req) - - errorResponse := &responses.ErrorResponse{} - assert.Equal(suite.T(), http.StatusBadRequest, rec.Code) - assert.NoError(suite.T(), json.NewDecoder(rec.Body).Decode(errorResponse)) - return errorResponse + return checkErrResponse(suite, rec) } func (suite *TestSuite) createPayInvoiceReq(payReq string, token string) *controllers.PayInvoiceResponseBody { @@ -224,11 +256,7 @@ func (suite *TestSuite) createPayInvoiceReqError(payReq string, token string) *r req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON) req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token)) suite.echo.ServeHTTP(rec, req) - - errorResponse := &responses.ErrorResponse{} - assert.Equal(suite.T(), http.StatusBadRequest, rec.Code) - assert.NoError(suite.T(), json.NewDecoder(rec.Body).Decode(errorResponse)) - return errorResponse + return checkErrResponse(suite, rec) } func (suite *TestSuite) createPayInvoiceReqWithCancel(payReq string, token string) { diff --git a/lib/service/user.go b/lib/service/user.go index 766a3bb..e8bc4ac 100644 --- a/lib/service/user.go +++ b/lib/service/user.go @@ -65,6 +65,16 @@ func (svc *LndhubService) FindUser(ctx context.Context, userId int64) (*models.U return &user, nil } +func (svc *LndhubService) FindUserByLogin(ctx context.Context, login string) (*models.User, error) { + var user models.User + + err := svc.DB.NewSelect().Model(&user).Where("login = ?", login).Limit(1).Scan(ctx) + if err != nil { + return &user, err + } + return &user, nil +} + func (svc *LndhubService) CurrentUserBalance(ctx context.Context, userId int64) (int64, error) { var balance int64 diff --git a/main.go b/main.go index a54f6c0..f7bc90a 100644 --- a/main.go +++ b/main.go @@ -128,6 +128,7 @@ func main() { // Public endpoints for account creation and authentication e.POST("/auth", controllers.NewAuthController(svc).Auth) e.POST("/create", controllers.NewCreateUserController(svc).CreateUser, strictRateLimitMiddleware) + e.POST("/invoice/:user_login", controllers.NewInvoiceController(svc).Invoice, middleware.RateLimiter(middleware.NewRateLimiterMemoryStore(rate.Limit(c.DefaultRateLimit)))) // Secured endpoints which require a Authorization token (JWT) secured := e.Group("", tokens.Middleware(c.JWTSecret), middleware.RateLimiter(middleware.NewRateLimiterMemoryStore(rate.Limit(c.DefaultRateLimit))))