From 9cd89fc411343ff4f026854eac68cb17c9d97e1d Mon Sep 17 00:00:00 2001 From: elraphty Date: Thu, 17 Oct 2024 09:12:56 +0100 Subject: [PATCH 01/24] added convert to timestamp function and test --- db/workspaces.go | 2 +- handlers/workspaces.go | 4 ++++ routes/workspaces.go | 2 ++ utils/helpers.go | 31 ++++++++++++++++++++++++++++++- utils/helpers_test.go | 11 +++++++++++ 5 files changed, 48 insertions(+), 2 deletions(-) diff --git a/db/workspaces.go b/db/workspaces.go index e7329458c..fad1e63ce 100644 --- a/db/workspaces.go +++ b/db/workspaces.go @@ -487,7 +487,7 @@ func (db database) GetPaymentHistory(workspace_uuid string, r *http.Request) []N func (db database) GetPendingPaymentHistory() []NewPaymentHistory { paymentHistories := []NewPaymentHistory{} - query := `SELECT * FROM payment_histories WHERE payment_status = '` + PaymentPending + `' AND status = true ORDER BY created DESC` + query := `SELECT * FROM payment_histories WHERE payment_status = '` + PaymentPending + `' AND status = true AND payment_type = 'payment' ORDER BY created DESC` db.db.Raw(query).Find(&paymentHistories) return paymentHistories diff --git a/handlers/workspaces.go b/handlers/workspaces.go index 1f16e04ed..3474f951d 100644 --- a/handlers/workspaces.go +++ b/handlers/workspaces.go @@ -1020,6 +1020,10 @@ func (oh *workspaceHandler) GetFeaturesByWorkspaceUuid(w http.ResponseWriter, r json.NewEncoder(w).Encode(workspaceFeatures) } +func (oh *workspaceHandler) GetLastWithdrawal(w http.ResponseWriter, r *http.Request) { + +} + func GetAllUserWorkspaces(pubkey string) []db.Workspace { // get the workspaces created by the user, then get all the workspaces // the user has been added to, loop through to get the workspace diff --git a/routes/workspaces.go b/routes/workspaces.go index e5bbd9d10..c829bbc4c 100644 --- a/routes/workspaces.go +++ b/routes/workspaces.go @@ -51,6 +51,8 @@ func WorkspaceRoutes() chi.Router { r.Get("/{workspace_uuid}/features", workspaceHandlers.GetFeaturesByWorkspaceUuid) r.Get("/{workspace_uuid}/repository/{uuid}", workspaceHandlers.GetWorkspaceRepoByWorkspaceUuidAndRepoUuid) r.Delete("/{workspace_uuid}/repository/{uuid}", workspaceHandlers.DeleteWorkspaceRepository) + + r.Get("/{workspace_uuid}/lastwithdrawal", workspaceHandlers.GetLastWithdrawal) }) return r } diff --git a/utils/helpers.go b/utils/helpers.go index e6a826ae2..7a3b6b1a7 100644 --- a/utils/helpers.go +++ b/utils/helpers.go @@ -5,6 +5,7 @@ import ( "encoding/base32" "fmt" "strconv" + "strings" "time" decodepay "github.com/nbd-wtf/ln-decodepay" @@ -71,9 +72,37 @@ func GetInvoiceExpired(paymentRequest string) bool { } } +func ConvertTimeToTimestamp(date string) int { + format := "2006-01-02 15:04:05" + + dateTouse := date + + if strings.Contains(date, "+") { + dateSplit := strings.Split(date, "+") + dateTouse = dateSplit[0] + } + + t, err := time.Parse(format, dateTouse) + if err != nil { + fmt.Println(err) + } else { + t = t.Add(time.Hour * 100) + fmt.Println(t.Unix()) + return int(t.Unix()) + } + return 0 +} + func GetDateDaysDifference(createdDate int64, paidDate *time.Time) int64 { firstDate := time.Unix(createdDate, 0) - difference := paidDate.Sub(*&firstDate) + difference := paidDate.Sub(firstDate) days := int64(difference.Hours() / 24) return days } + +func GetHoursDifference(createdDate int64, paidDate *time.Time) int64 { + firstDate := time.Unix(createdDate, 0) + difference := paidDate.Sub(firstDate) + hours := int64(difference.Hours()) + return hours +} diff --git a/utils/helpers_test.go b/utils/helpers_test.go index 6fdaa9c31..adaca8846 100644 --- a/utils/helpers_test.go +++ b/utils/helpers_test.go @@ -78,3 +78,14 @@ func TestGetInvoiceExpired(t *testing.T) { isInvoiceExpired := GetInvoiceExpired(expiredInvoice) assert.Equal(t, true, isInvoiceExpired) } + +func TestConvertTimeToTimestamp(t *testing.T) { + dateWithPlus := "2024-10-16 09:21:21.743327+00" + dateWithoutPlus := "2024-10-16 09:21:21.743327" + + dateTimestamp1 := ConvertTimeToTimestamp(dateWithPlus) + dateTimestamp2 := ConvertTimeToTimestamp(dateWithoutPlus) + + assert.Greater(t, dateTimestamp1, 7000000) + assert.Greater(t, dateTimestamp2, 7000000) +} From 9f8b6fcd3073c35fa1f05413df03fc19b8f4905f Mon Sep 17 00:00:00 2001 From: elraphty Date: Thu, 17 Oct 2024 10:05:09 +0100 Subject: [PATCH 02/24] tested get hours difference --- utils/helpers.go | 2 +- utils/helpers_test.go | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/utils/helpers.go b/utils/helpers.go index 7a3b6b1a7..23030254c 100644 --- a/utils/helpers.go +++ b/utils/helpers.go @@ -86,7 +86,7 @@ func ConvertTimeToTimestamp(date string) int { if err != nil { fmt.Println(err) } else { - t = t.Add(time.Hour * 100) + t = t.Add(time.Hour * 1) fmt.Println(t.Unix()) return int(t.Unix()) } diff --git a/utils/helpers_test.go b/utils/helpers_test.go index adaca8846..5b9f08a4e 100644 --- a/utils/helpers_test.go +++ b/utils/helpers_test.go @@ -89,3 +89,11 @@ func TestConvertTimeToTimestamp(t *testing.T) { assert.Greater(t, dateTimestamp1, 7000000) assert.Greater(t, dateTimestamp2, 7000000) } + +func TestGetHoursDifference(t *testing.T) { + time1 := time.Now().Unix() + time2 := time.Now().Add(time.Hour * 1) + + hourDiff := GetHoursDifference(time1, &time2) + assert.Equal(t, hourDiff, int64(1)) +} From e83226d89798825385b12478c7f78cf4020a1039 Mon Sep 17 00:00:00 2001 From: elraphty Date: Thu, 17 Oct 2024 10:35:13 +0100 Subject: [PATCH 03/24] test time to hours --- utils/helpers.go | 14 ++++++++++---- utils/helpers_test.go | 8 ++++++++ 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/utils/helpers.go b/utils/helpers.go index 23030254c..8c951da2e 100644 --- a/utils/helpers.go +++ b/utils/helpers.go @@ -74,7 +74,6 @@ func GetInvoiceExpired(paymentRequest string) bool { func ConvertTimeToTimestamp(date string) int { format := "2006-01-02 15:04:05" - dateTouse := date if strings.Contains(date, "+") { @@ -84,15 +83,22 @@ func ConvertTimeToTimestamp(date string) int { t, err := time.Parse(format, dateTouse) if err != nil { - fmt.Println(err) + fmt.Println("Parse string to timestamp", err) } else { - t = t.Add(time.Hour * 1) - fmt.Println(t.Unix()) return int(t.Unix()) } return 0 } +func AddHoursToTimestamp(timestamp int, hours int) int { + tm := time.Unix(int64(timestamp), 0) + + dur := int(time.Hour.Hours()) * hours + tm = tm.Add(time.Hour * time.Duration(dur)) + + return int(tm.Unix()) +} + func GetDateDaysDifference(createdDate int64, paidDate *time.Time) int64 { firstDate := time.Unix(createdDate, 0) difference := paidDate.Sub(firstDate) diff --git a/utils/helpers_test.go b/utils/helpers_test.go index 5b9f08a4e..f8e82f0a4 100644 --- a/utils/helpers_test.go +++ b/utils/helpers_test.go @@ -97,3 +97,11 @@ func TestGetHoursDifference(t *testing.T) { hourDiff := GetHoursDifference(time1, &time2) assert.Equal(t, hourDiff, int64(1)) } + +func TestAddHoursToTimestamp(t *testing.T) { + time1 := time.Now().Unix() + time2 := time.Now().Unix() + + hoursAdd := AddHoursToTimestamp(int(time2), 2) + assert.Greater(t, hoursAdd, int(time1)) +} From a300c684955a2dc33df29b69219208283916d2de Mon Sep 17 00:00:00 2001 From: elraphty Date: Thu, 17 Oct 2024 10:49:08 +0100 Subject: [PATCH 04/24] added function --- db/interface.go | 1 + db/workspaces.go | 6 ++++++ handlers/workspaces.go | 13 ++++++++++++ mocks/Database.go | 46 ++++++++++++++++++++++++++++++++++++++++++ utils/helpers.go | 4 ++-- 5 files changed, 68 insertions(+), 2 deletions(-) diff --git a/db/interface.go b/db/interface.go index 630ee9a91..2cf4b912b 100644 --- a/db/interface.go +++ b/db/interface.go @@ -126,6 +126,7 @@ type Database interface { ChangeWorkspaceDeleteStatus(workspace_uuid string, status bool) Workspace UpdateWorkspaceForDeletion(uuid string) error ProcessDeleteWorkspace(workspace_uuid string) error + GetLastWithdrawal(workspace_uuid string) NewPaymentHistory DeleteAllUsersFromWorkspace(uuid string) error GetFilterStatusCount() FilterStattuCount UserHasManageBountyRoles(pubKeyFromAuth string, uuid string) bool diff --git a/db/workspaces.go b/db/workspaces.go index fad1e63ce..666910531 100644 --- a/db/workspaces.go +++ b/db/workspaces.go @@ -612,6 +612,12 @@ func (db database) DeleteAllUsersFromWorkspace(workspace_uuid string) error { return nil } +func (db database) GetLastWithdrawal(workspace_uuid string) NewPaymentHistory { + p := NewPaymentHistory{} + db.db.Model(&NewPaymentHistory{}).Where("workspace_uuid", workspace_uuid).Where("payment_type", "withdraw").Order("created DESC").Limit(1).Find(&p) + return p +} + func (db database) GetFeaturePhasesBountiesCount(bountyType string, phaseUuid string) int64 { var count int64 diff --git a/handlers/workspaces.go b/handlers/workspaces.go index 3474f951d..ef6bfd4ca 100644 --- a/handlers/workspaces.go +++ b/handlers/workspaces.go @@ -1021,7 +1021,20 @@ func (oh *workspaceHandler) GetFeaturesByWorkspaceUuid(w http.ResponseWriter, r } func (oh *workspaceHandler) GetLastWithdrawal(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + pubKeyFromAuth, _ := ctx.Value(auth.ContextKey).(string) + if pubKeyFromAuth == "" { + fmt.Println("[workspaces] no pubkey from auth") + w.WriteHeader(http.StatusUnauthorized) + return + } + + workspace_uuid := chi.URLParam(r, "workspace_uuid") + lastWithdrawal := oh.db.GetLastWithdrawal(workspace_uuid) + now := time.Now().Unix() + withdrawTime := lastWithdrawal.Created + utils.GetHoursDifference(now, withdrawTime) } func GetAllUserWorkspaces(pubkey string) []db.Workspace { diff --git a/mocks/Database.go b/mocks/Database.go index 8cd6daf78..b395764ee 100644 --- a/mocks/Database.go +++ b/mocks/Database.go @@ -3587,6 +3587,52 @@ func (_c *Database_GetInvoice_Call) RunAndReturn(run func(string) db.NewInvoiceL return _c } +// GetLastWithdrawal provides a mock function with given fields: workspace_uuid +func (_m *Database) GetLastWithdrawal(workspace_uuid string) db.NewPaymentHistory { + ret := _m.Called(workspace_uuid) + + if len(ret) == 0 { + panic("no return value specified for GetLastWithdrawal") + } + + var r0 db.NewPaymentHistory + if rf, ok := ret.Get(0).(func(string) db.NewPaymentHistory); ok { + r0 = rf(workspace_uuid) + } else { + r0 = ret.Get(0).(db.NewPaymentHistory) + } + + return r0 +} + +// Database_GetLastWithdrawal_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetLastWithdrawal' +type Database_GetLastWithdrawal_Call struct { + *mock.Call +} + +// GetLastWithdrawal is a helper method to define mock.On call +// - workspace_uuid string +func (_e *Database_Expecter) GetLastWithdrawal(workspace_uuid interface{}) *Database_GetLastWithdrawal_Call { + return &Database_GetLastWithdrawal_Call{Call: _e.mock.On("GetLastWithdrawal", workspace_uuid)} +} + +func (_c *Database_GetLastWithdrawal_Call) Run(run func(workspace_uuid string)) *Database_GetLastWithdrawal_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string)) + }) + return _c +} + +func (_c *Database_GetLastWithdrawal_Call) Return(_a0 db.NewPaymentHistory) *Database_GetLastWithdrawal_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Database_GetLastWithdrawal_Call) RunAndReturn(run func(string) db.NewPaymentHistory) *Database_GetLastWithdrawal_Call { + _c.Call.Return(run) + return _c +} + // GetLeaderBoard provides a mock function with given fields: uuid func (_m *Database) GetLeaderBoard(uuid string) []db.LeaderBoard { ret := _m.Called(uuid) diff --git a/utils/helpers.go b/utils/helpers.go index 8c951da2e..a036bcb26 100644 --- a/utils/helpers.go +++ b/utils/helpers.go @@ -106,9 +106,9 @@ func GetDateDaysDifference(createdDate int64, paidDate *time.Time) int64 { return days } -func GetHoursDifference(createdDate int64, paidDate *time.Time) int64 { +func GetHoursDifference(createdDate int64, endDate *time.Time) int64 { firstDate := time.Unix(createdDate, 0) - difference := paidDate.Sub(firstDate) + difference := endDate.Sub(firstDate) hours := int64(difference.Hours()) return hours } From 1720006df530ca50170827a52e6e5e1449c9ec78 Mon Sep 17 00:00:00 2001 From: elraphty Date: Thu, 17 Oct 2024 21:58:18 +0100 Subject: [PATCH 05/24] checked for sum of deposits and withdrawals --- db/interface.go | 2 + db/workspaces.go | 14 +++++++ handlers/bounty.go | 56 +++++++++++++++++++++++++ handlers/workspaces.go | 5 ++- mocks/Database.go | 92 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 168 insertions(+), 1 deletion(-) diff --git a/db/interface.go b/db/interface.go index 2cf4b912b..d3473f6bd 100644 --- a/db/interface.go +++ b/db/interface.go @@ -127,6 +127,8 @@ type Database interface { UpdateWorkspaceForDeletion(uuid string) error ProcessDeleteWorkspace(workspace_uuid string) error GetLastWithdrawal(workspace_uuid string) NewPaymentHistory + GetSumOfDeposits(workspace_uuid string) uint + GetSumOfWithdrawal(workspace_uuid string) uint DeleteAllUsersFromWorkspace(uuid string) error GetFilterStatusCount() FilterStattuCount UserHasManageBountyRoles(pubKeyFromAuth string, uuid string) bool diff --git a/db/workspaces.go b/db/workspaces.go index 666910531..285661fa2 100644 --- a/db/workspaces.go +++ b/db/workspaces.go @@ -618,6 +618,20 @@ func (db database) GetLastWithdrawal(workspace_uuid string) NewPaymentHistory { return p } +func (db database) GetSumOfDeposits(workspace_uuid string) uint { + var depositAmount uint + db.db.Model(&NewPaymentHistory{}).Where("workspace_uuid = ?", workspace_uuid).Where("status = ?", true).Where("payment_status = ?", "deposit").Select("SUM(amount)").Row().Scan(&depositAmount) + + return depositAmount +} + +func (db database) GetSumOfWithdrawal(workspace_uuid string) uint { + var depositAmount uint + db.db.Model(&NewPaymentHistory{}).Where("workspace_uuid = ?", workspace_uuid).Where("status = ?", true).Where("payment_status = ?", "withdraw").Select("SUM(amount)").Row().Scan(&depositAmount) + + return depositAmount +} + func (db database) GetFeaturePhasesBountiesCount(bountyType string, phaseUuid string) int64 { var count int64 diff --git a/handlers/bounty.go b/handlers/bounty.go index 037d10607..737ddcff7 100644 --- a/handlers/bounty.go +++ b/handlers/bounty.go @@ -927,6 +927,21 @@ func (h *bountyHandler) BountyBudgetWithdraw(w http.ResponseWriter, r *http.Requ return } + lastWithdrawal := h.db.GetLastWithdrawal(request.OrgUuid) + + now := time.Now().Unix() + withdrawTime := lastWithdrawal.Created + hoursDiff := utils.GetHoursDifference(now, withdrawTime) + + // Check that last withdraw time is greater than 1 + if hoursDiff < 1 { + w.WriteHeader(http.StatusUnauthorized) + errMsg := formatPayError("Your last withdrawal is not more than an hour ago") + json.NewEncoder(w).Encode(errMsg) + h.m.Unlock() + return + } + log.Printf("[bounty] [BountyBudgetWithdraw] Logging body: workspace_uuid: %s, pubkey: %s, invoice: %s", request.OrgUuid, pubKeyFromAuth, request.PaymentRequest) // check if user is the admin of the workspace @@ -943,6 +958,18 @@ func (h *bountyHandler) BountyBudgetWithdraw(w http.ResponseWriter, r *http.Requ amount := utils.GetInvoiceAmount(request.PaymentRequest) if amount > 0 { + // Check that the deposit is more than the withdrawal plus amount to withdraw + sumOfWithdrawals := h.db.GetSumOfWithdrawal(request.OrgUuid) + sumOfDeposits := h.db.GetSumOfWithdrawal(request.OrgUuid) + + if sumOfDeposits < sumOfWithdrawals+amount { + w.WriteHeader(http.StatusUnauthorized) + errMsg := formatPayError("Your deposits is lesser than your withdral") + json.NewEncoder(w).Encode(errMsg) + h.m.Unlock() + return + } + // check if the workspace bounty balance // is greater than the amount orgBudget := h.db.GetWorkspaceBudget(request.OrgUuid) @@ -1003,6 +1030,23 @@ func (h *bountyHandler) NewBountyBudgetWithdraw(w http.ResponseWriter, r *http.R return } + lastWithdrawal := h.db.GetLastWithdrawal(request.WorkspaceUuid) + + now := time.Now().Unix() + withdrawTime := lastWithdrawal.Created + hoursDiff := utils.GetHoursDifference(now, withdrawTime) + + // Check that last withdraw time is greater than 1 + if hoursDiff < 1 { + w.WriteHeader(http.StatusUnauthorized) + errMsg := formatPayError("Your last withdrawal is not more than an hour ago") + json.NewEncoder(w).Encode(errMsg) + h.m.Unlock() + return + } + + log.Printf("[bounty] [BountyBudgetWithdraw] Logging body: workspace_uuid: %s, pubkey: %s, invoice: %s", request.WorkspaceUuid, pubKeyFromAuth, request.PaymentRequest) + // check if user is the admin of the workspace // or has a withdraw bounty budget role hasRole := h.userHasAccess(pubKeyFromAuth, request.WorkspaceUuid, db.WithdrawBudget) @@ -1017,6 +1061,18 @@ func (h *bountyHandler) NewBountyBudgetWithdraw(w http.ResponseWriter, r *http.R amount := utils.GetInvoiceAmount(request.PaymentRequest) if amount > 0 { + // Check that the deposit is more than the withdrawal plus amount to withdraw + sumOfWithdrawals := h.db.GetSumOfWithdrawal(request.WorkspaceUuid) + sumOfDeposits := h.db.GetSumOfWithdrawal(request.WorkspaceUuid) + + if sumOfDeposits < sumOfWithdrawals+amount { + w.WriteHeader(http.StatusUnauthorized) + errMsg := formatPayError("Your deposits is lesser than your withdral") + json.NewEncoder(w).Encode(errMsg) + h.m.Unlock() + return + } + // check if the workspace bounty balance // is greater than the amount orgBudget := h.db.GetWorkspaceBudget(request.WorkspaceUuid) diff --git a/handlers/workspaces.go b/handlers/workspaces.go index ef6bfd4ca..f6e60c2b7 100644 --- a/handlers/workspaces.go +++ b/handlers/workspaces.go @@ -1034,7 +1034,10 @@ func (oh *workspaceHandler) GetLastWithdrawal(w http.ResponseWriter, r *http.Req now := time.Now().Unix() withdrawTime := lastWithdrawal.Created - utils.GetHoursDifference(now, withdrawTime) + hoursDiff := utils.GetHoursDifference(now, withdrawTime) + + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(hoursDiff) } func GetAllUserWorkspaces(pubkey string) []db.Workspace { diff --git a/mocks/Database.go b/mocks/Database.go index b395764ee..4c40aeb6b 100644 --- a/mocks/Database.go +++ b/mocks/Database.go @@ -4933,6 +4933,98 @@ func (_c *Database_GetPreviousWorkspaceBountyByCreated_Call) RunAndReturn(run fu return _c } +// GetSumOfDeposits provides a mock function with given fields: workspace_uuid +func (_m *Database) GetSumOfDeposits(workspace_uuid string) uint { + ret := _m.Called(workspace_uuid) + + if len(ret) == 0 { + panic("no return value specified for GetSumOfDeposits") + } + + var r0 uint + if rf, ok := ret.Get(0).(func(string) uint); ok { + r0 = rf(workspace_uuid) + } else { + r0 = ret.Get(0).(uint) + } + + return r0 +} + +// Database_GetSumOfDeposits_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetSumOfDeposits' +type Database_GetSumOfDeposits_Call struct { + *mock.Call +} + +// GetSumOfDeposits is a helper method to define mock.On call +// - workspace_uuid string +func (_e *Database_Expecter) GetSumOfDeposits(workspace_uuid interface{}) *Database_GetSumOfDeposits_Call { + return &Database_GetSumOfDeposits_Call{Call: _e.mock.On("GetSumOfDeposits", workspace_uuid)} +} + +func (_c *Database_GetSumOfDeposits_Call) Run(run func(workspace_uuid string)) *Database_GetSumOfDeposits_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string)) + }) + return _c +} + +func (_c *Database_GetSumOfDeposits_Call) Return(_a0 uint) *Database_GetSumOfDeposits_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Database_GetSumOfDeposits_Call) RunAndReturn(run func(string) uint) *Database_GetSumOfDeposits_Call { + _c.Call.Return(run) + return _c +} + +// GetSumOfWithdrawal provides a mock function with given fields: workspace_uuid +func (_m *Database) GetSumOfWithdrawal(workspace_uuid string) uint { + ret := _m.Called(workspace_uuid) + + if len(ret) == 0 { + panic("no return value specified for GetSumOfWithdrawal") + } + + var r0 uint + if rf, ok := ret.Get(0).(func(string) uint); ok { + r0 = rf(workspace_uuid) + } else { + r0 = ret.Get(0).(uint) + } + + return r0 +} + +// Database_GetSumOfWithdrawal_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetSumOfWithdrawal' +type Database_GetSumOfWithdrawal_Call struct { + *mock.Call +} + +// GetSumOfWithdrawal is a helper method to define mock.On call +// - workspace_uuid string +func (_e *Database_Expecter) GetSumOfWithdrawal(workspace_uuid interface{}) *Database_GetSumOfWithdrawal_Call { + return &Database_GetSumOfWithdrawal_Call{Call: _e.mock.On("GetSumOfWithdrawal", workspace_uuid)} +} + +func (_c *Database_GetSumOfWithdrawal_Call) Run(run func(workspace_uuid string)) *Database_GetSumOfWithdrawal_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string)) + }) + return _c +} + +func (_c *Database_GetSumOfWithdrawal_Call) Return(_a0 uint) *Database_GetSumOfWithdrawal_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Database_GetSumOfWithdrawal_Call) RunAndReturn(run func(string) uint) *Database_GetSumOfWithdrawal_Call { + _c.Call.Return(run) + return _c +} + // GetTribe provides a mock function with given fields: uuid func (_m *Database) GetTribe(uuid string) db.Tribe { ret := _m.Called(uuid) From 3cd4997928aebf0656d8215631fa2fd12b968d32 Mon Sep 17 00:00:00 2001 From: elraphty Date: Thu, 17 Oct 2024 22:52:36 +0100 Subject: [PATCH 06/24] check that user has done a withdrawal --- handlers/bounty.go | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/handlers/bounty.go b/handlers/bounty.go index 737ddcff7..39aafd463 100644 --- a/handlers/bounty.go +++ b/handlers/bounty.go @@ -929,17 +929,20 @@ func (h *bountyHandler) BountyBudgetWithdraw(w http.ResponseWriter, r *http.Requ lastWithdrawal := h.db.GetLastWithdrawal(request.OrgUuid) - now := time.Now().Unix() - withdrawTime := lastWithdrawal.Created - hoursDiff := utils.GetHoursDifference(now, withdrawTime) + if lastWithdrawal.ID > 0 { - // Check that last withdraw time is greater than 1 - if hoursDiff < 1 { - w.WriteHeader(http.StatusUnauthorized) - errMsg := formatPayError("Your last withdrawal is not more than an hour ago") - json.NewEncoder(w).Encode(errMsg) - h.m.Unlock() - return + now := time.Now().Unix() + withdrawTime := lastWithdrawal.Created + hoursDiff := utils.GetHoursDifference(now, withdrawTime) + + // Check that last withdraw time is greater than 1 + if hoursDiff < 1 { + w.WriteHeader(http.StatusUnauthorized) + errMsg := formatPayError("Your last withdrawal is not more than an hour ago") + json.NewEncoder(w).Encode(errMsg) + h.m.Unlock() + return + } } log.Printf("[bounty] [BountyBudgetWithdraw] Logging body: workspace_uuid: %s, pubkey: %s, invoice: %s", request.OrgUuid, pubKeyFromAuth, request.PaymentRequest) From a29a7fdeb2800b62d40a7a6ddc83e2cf3c01e15e Mon Sep 17 00:00:00 2001 From: elraphty Date: Fri, 18 Oct 2024 09:29:17 +0100 Subject: [PATCH 07/24] fixed withdraw bug --- db/workspaces.go | 4 ++-- handlers/bounty.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/db/workspaces.go b/db/workspaces.go index 285661fa2..767ffa942 100644 --- a/db/workspaces.go +++ b/db/workspaces.go @@ -620,14 +620,14 @@ func (db database) GetLastWithdrawal(workspace_uuid string) NewPaymentHistory { func (db database) GetSumOfDeposits(workspace_uuid string) uint { var depositAmount uint - db.db.Model(&NewPaymentHistory{}).Where("workspace_uuid = ?", workspace_uuid).Where("status = ?", true).Where("payment_status = ?", "deposit").Select("SUM(amount)").Row().Scan(&depositAmount) + db.db.Model(&NewPaymentHistory{}).Where("workspace_uuid = ?", workspace_uuid).Where("status = ?", true).Where("payment_type = ?", "deposit").Select("SUM(amount)").Row().Scan(&depositAmount) return depositAmount } func (db database) GetSumOfWithdrawal(workspace_uuid string) uint { var depositAmount uint - db.db.Model(&NewPaymentHistory{}).Where("workspace_uuid = ?", workspace_uuid).Where("status = ?", true).Where("payment_status = ?", "withdraw").Select("SUM(amount)").Row().Scan(&depositAmount) + db.db.Model(&NewPaymentHistory{}).Where("workspace_uuid = ?", workspace_uuid).Where("status = ?", true).Where("payment_type = ?", "withdraw").Select("SUM(amount)").Row().Scan(&depositAmount) return depositAmount } diff --git a/handlers/bounty.go b/handlers/bounty.go index 39aafd463..c6e1ed31f 100644 --- a/handlers/bounty.go +++ b/handlers/bounty.go @@ -963,11 +963,11 @@ func (h *bountyHandler) BountyBudgetWithdraw(w http.ResponseWriter, r *http.Requ if amount > 0 { // Check that the deposit is more than the withdrawal plus amount to withdraw sumOfWithdrawals := h.db.GetSumOfWithdrawal(request.OrgUuid) - sumOfDeposits := h.db.GetSumOfWithdrawal(request.OrgUuid) + sumOfDeposits := h.db.GetSumOfDeposits(request.OrgUuid) if sumOfDeposits < sumOfWithdrawals+amount { w.WriteHeader(http.StatusUnauthorized) - errMsg := formatPayError("Your deposits is lesser than your withdral") + errMsg := formatPayError("Your deposits is lesser than your withdrawal") json.NewEncoder(w).Encode(errMsg) h.m.Unlock() return From fccbcfe0ca34cf0a3180b1ac65c95ec2a3d9af9f Mon Sep 17 00:00:00 2001 From: elraphty Date: Fri, 18 Oct 2024 11:08:56 +0100 Subject: [PATCH 08/24] fixed hour check error --- handlers/bounty.go | 47 ++++++++++++++++++++---------------- handlers/bounty_test.go | 53 +++++++++++++++++++++++++++++++++++++++++ utils/helpers.go | 3 ++- 3 files changed, 82 insertions(+), 21 deletions(-) diff --git a/handlers/bounty.go b/handlers/bounty.go index c6e1ed31f..13538a53a 100644 --- a/handlers/bounty.go +++ b/handlers/bounty.go @@ -931,14 +931,16 @@ func (h *bountyHandler) BountyBudgetWithdraw(w http.ResponseWriter, r *http.Requ if lastWithdrawal.ID > 0 { - now := time.Now().Unix() - withdrawTime := lastWithdrawal.Created - hoursDiff := utils.GetHoursDifference(now, withdrawTime) + now := time.Now() + withdrawCreated := lastWithdrawal.Created + withdrawTime := utils.ConvertTimeToTimestamp(withdrawCreated.String()) + hoursDiff := utils.GetHoursDifference(int64(withdrawTime), &now) // Check that last withdraw time is greater than 1 if hoursDiff < 1 { - w.WriteHeader(http.StatusUnauthorized) - errMsg := formatPayError("Your last withdrawal is not more than an hour ago") + w.WriteHeader(http.StatusForbidden) + errMsg := formatPayError("Your last withdrawal is not more than an hour ago") + log.Println("Your last withdrawal is not more than an hour ago", hoursDiff, lastWithdrawal.Created) json.NewEncoder(w).Encode(errMsg) h.m.Unlock() return @@ -961,28 +963,31 @@ func (h *bountyHandler) BountyBudgetWithdraw(w http.ResponseWriter, r *http.Requ amount := utils.GetInvoiceAmount(request.PaymentRequest) if amount > 0 { + // check if the workspace bounty balance + // is greater than the amount + orgBudget := h.db.GetWorkspaceBudget(request.OrgUuid) + if amount > orgBudget.TotalBudget { + w.WriteHeader(http.StatusForbidden) + errMsg := formatPayError("Workspace budget is not enough to withdraw the amount") + json.NewEncoder(w).Encode(errMsg) + h.m.Unlock() + return + } + // Check that the deposit is more than the withdrawal plus amount to withdraw sumOfWithdrawals := h.db.GetSumOfWithdrawal(request.OrgUuid) sumOfDeposits := h.db.GetSumOfDeposits(request.OrgUuid) if sumOfDeposits < sumOfWithdrawals+amount { - w.WriteHeader(http.StatusUnauthorized) + w.WriteHeader(http.StatusForbidden) + + log.Printf("Sum of deposits is lesser than withdrawal: %d : %d", sumOfDeposits, sumOfWithdrawals) errMsg := formatPayError("Your deposits is lesser than your withdrawal") json.NewEncoder(w).Encode(errMsg) h.m.Unlock() return } - // check if the workspace bounty balance - // is greater than the amount - orgBudget := h.db.GetWorkspaceBudget(request.OrgUuid) - if amount > orgBudget.TotalBudget { - w.WriteHeader(http.StatusForbidden) - errMsg := formatPayError("Workspace budget is not enough to withdraw the amount") - json.NewEncoder(w).Encode(errMsg) - h.m.Unlock() - return - } paymentSuccess, paymentError := h.PayLightningInvoice(request.PaymentRequest) if paymentSuccess.Success { // withdraw amount from workspace budget @@ -990,6 +995,7 @@ func (h *bountyHandler) BountyBudgetWithdraw(w http.ResponseWriter, r *http.Requ w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(paymentSuccess) } else { + log.Printf("Withdrawal payment failed: %s ", paymentError.Error) w.WriteHeader(http.StatusBadRequest) json.NewEncoder(w).Encode(paymentError) } @@ -1035,9 +1041,10 @@ func (h *bountyHandler) NewBountyBudgetWithdraw(w http.ResponseWriter, r *http.R lastWithdrawal := h.db.GetLastWithdrawal(request.WorkspaceUuid) - now := time.Now().Unix() - withdrawTime := lastWithdrawal.Created - hoursDiff := utils.GetHoursDifference(now, withdrawTime) + now := time.Now() + withdrawCreated := lastWithdrawal.Created + withdrawTime := utils.ConvertTimeToTimestamp(withdrawCreated.String()) + hoursDiff := utils.GetHoursDifference(int64(withdrawTime), &now) // Check that last withdraw time is greater than 1 if hoursDiff < 1 { @@ -1066,7 +1073,7 @@ func (h *bountyHandler) NewBountyBudgetWithdraw(w http.ResponseWriter, r *http.R if amount > 0 { // Check that the deposit is more than the withdrawal plus amount to withdraw sumOfWithdrawals := h.db.GetSumOfWithdrawal(request.WorkspaceUuid) - sumOfDeposits := h.db.GetSumOfWithdrawal(request.WorkspaceUuid) + sumOfDeposits := h.db.GetSumOfDeposits(request.WorkspaceUuid) if sumOfDeposits < sumOfWithdrawals+amount { w.WriteHeader(http.StatusUnauthorized) diff --git a/handlers/bounty_test.go b/handlers/bounty_test.go index 4cece13dc..f6a9622ad 100644 --- a/handlers/bounty_test.go +++ b/handlers/bounty_test.go @@ -1786,6 +1786,23 @@ func TestBountyBudgetWithdraw(t *testing.T) { db.TestDB.CreateOrEditWorkspace(workspace) budgetAmount := uint(5000) + + paymentTime := time.Now() + + payment := db.NewPaymentHistory{ + Amount: budgetAmount, + WorkspaceUuid: workspace.Uuid, + PaymentType: db.Deposit, + SenderPubKey: person.OwnerPubKey, + ReceiverPubKey: person.OwnerPubKey, + Tag: "test_deposit", + Status: true, + Created: &paymentTime, + Updated: &paymentTime, + } + + db.TestDB.AddPaymentHistory(payment) + budget := db.NewBountyBudget{ WorkspaceUuid: workspace.Uuid, TotalBudget: budgetAmount, @@ -1940,6 +1957,23 @@ func TestBountyBudgetWithdraw(t *testing.T) { invoice := "lnbcrt10u1pnv7nz6dqld9h8vmmfvdjjqen0wgsrzvpsxqcrqvqpp54v0synj4q3j2usthzt8g5umteky6d2apvgtaxd7wkepkygxgqdyssp5lhv2878qjas3azv3nnu8r6g3tlgejl7mu7cjzc9q5haygrpapd4s9qrsgqcqpjxqrrssrzjqgtzc5n3vcmlhqfq4vpxreqskxzay6xhdrxx7c38ckqs95v5459uyqqqqyqqtwsqqgqqqqqqqqqqqqqq9gea2fjj7q302ncprk2pawk4zdtayycvm0wtjpprml96h9vujvmqdp0n5z8v7lqk44mq9620jszwaevj0mws7rwd2cegxvlmfszwgpgfqp2xafjf" bHandler.userHasAccess = handlerUserHasAccess + // add a zero amount withdrawal with a time lesser than 2 hours to beat the 1 hour withdrawal timer + paymentTime := time.Now().Add(-time.Hour * 2) + + payment := db.NewPaymentHistory{ + Amount: 0, + WorkspaceUuid: workspace.Uuid, + PaymentType: db.Withdraw, + SenderPubKey: person.OwnerPubKey, + ReceiverPubKey: person.OwnerPubKey, + Tag: "test_withdraw_before_loop", + Status: true, + Created: &paymentTime, + Updated: &paymentTime, + } + + db.TestDB.AddPaymentHistory(payment) + for i := 0; i < 3; i++ { expectedFinalBudget := initialBudget - (paymentAmount * uint(i+1)) mockHttpClient.ExpectedCalls = nil @@ -1968,6 +2002,25 @@ func TestBountyBudgetWithdraw(t *testing.T) { finalBudget := db.TestDB.GetWorkspaceBudget(workspace.Uuid) assert.Equal(t, expectedFinalBudget, finalBudget.TotalBudget, "The workspace's final budget should reflect the deductions from the successful withdrawals") + + // add a zero amount withdrawal with a time lesser than 2 hours to beat the 1 hour withdrawal timer + paymentTime := time.Now().Add(-time.Hour * 2) + + tag := fmt.Sprintf("test_withdraw:%d", i) + + payment := db.NewPaymentHistory{ + Amount: 0, + WorkspaceUuid: workspace.Uuid, + PaymentType: db.Withdraw, + SenderPubKey: person.OwnerPubKey, + ReceiverPubKey: person.OwnerPubKey, + Tag: tag, + Status: true, + Created: &paymentTime, + Updated: &paymentTime, + } + + db.TestDB.AddPaymentHistory(payment) } }) diff --git a/utils/helpers.go b/utils/helpers.go index a036bcb26..2b51ef58c 100644 --- a/utils/helpers.go +++ b/utils/helpers.go @@ -78,7 +78,7 @@ func ConvertTimeToTimestamp(date string) int { if strings.Contains(date, "+") { dateSplit := strings.Split(date, "+") - dateTouse = dateSplit[0] + dateTouse = strings.Trim(dateSplit[0], " ") } t, err := time.Parse(format, dateTouse) @@ -109,6 +109,7 @@ func GetDateDaysDifference(createdDate int64, paidDate *time.Time) int64 { func GetHoursDifference(createdDate int64, endDate *time.Time) int64 { firstDate := time.Unix(createdDate, 0) difference := endDate.Sub(firstDate) + hours := int64(difference.Hours()) return hours } From 99887b119c0d78a719347c0eed56ed8d6abd7c9b Mon Sep 17 00:00:00 2001 From: elraphty Date: Fri, 18 Oct 2024 11:41:35 +0100 Subject: [PATCH 09/24] added payment_history to 400 test error --- handlers/bounty_test.go | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/handlers/bounty_test.go b/handlers/bounty_test.go index f6a9622ad..5446ea6ed 100644 --- a/handlers/bounty_test.go +++ b/handlers/bounty_test.go @@ -1922,6 +1922,22 @@ func TestBountyBudgetWithdraw(t *testing.T) { }) t.Run("400 BadRequest error if there is an error with invoice payment", func(t *testing.T) { + // add a zero amount withdrawal with a time lesser than 2 hours to beat the 1 hour withdrawal timer + paymentTime := time.Now().Add(-time.Hour * 2) + + payment := db.NewPaymentHistory{ + Amount: 0, + WorkspaceUuid: workspace.Uuid, + PaymentType: db.Withdraw, + SenderPubKey: person.OwnerPubKey, + ReceiverPubKey: person.OwnerPubKey, + Tag: "test_withdraw_before_400_error", + Status: true, + Created: &paymentTime, + Updated: &paymentTime, + } + + db.TestDB.AddPaymentHistory(payment) mockHttpClient.On("Do", mock.AnythingOfType("*http.Request")).Return(&http.Response{ StatusCode: 400, @@ -2003,8 +2019,9 @@ func TestBountyBudgetWithdraw(t *testing.T) { finalBudget := db.TestDB.GetWorkspaceBudget(workspace.Uuid) assert.Equal(t, expectedFinalBudget, finalBudget.TotalBudget, "The workspace's final budget should reflect the deductions from the successful withdrawals") - // add a zero amount withdrawal with a time lesser than 2 hours to beat the 1 hour withdrawal timer - paymentTime := time.Now().Add(-time.Hour * 2) + // add a zero amount withdrawal with a time lesser than 2 + loop index hours to beat the 1 hour withdrawal timer + dur := int(time.Hour.Hours())*2 + i + paymentTime = time.Now().Add(-time.Hour * time.Duration(dur)) tag := fmt.Sprintf("test_withdraw:%d", i) From 7efdd301b1fa00f8eb10b062491a2dbd85e27ebd Mon Sep 17 00:00:00 2001 From: elraphty Date: Fri, 18 Oct 2024 12:12:29 +0100 Subject: [PATCH 10/24] sleep after 2 seconds after adding payment to histiry --- handlers/bounty_test.go | 60 +++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 36 deletions(-) diff --git a/handlers/bounty_test.go b/handlers/bounty_test.go index 5446ea6ed..706d44994 100644 --- a/handlers/bounty_test.go +++ b/handlers/bounty_test.go @@ -1939,6 +1939,8 @@ func TestBountyBudgetWithdraw(t *testing.T) { db.TestDB.AddPaymentHistory(payment) + time.Sleep(2 * time.Second) + mockHttpClient.On("Do", mock.AnythingOfType("*http.Request")).Return(&http.Response{ StatusCode: 400, Body: io.NopCloser(bytes.NewBufferString(`{"success": false, "error": "Payment error"}`)), @@ -1973,28 +1975,33 @@ func TestBountyBudgetWithdraw(t *testing.T) { invoice := "lnbcrt10u1pnv7nz6dqld9h8vmmfvdjjqen0wgsrzvpsxqcrqvqpp54v0synj4q3j2usthzt8g5umteky6d2apvgtaxd7wkepkygxgqdyssp5lhv2878qjas3azv3nnu8r6g3tlgejl7mu7cjzc9q5haygrpapd4s9qrsgqcqpjxqrrssrzjqgtzc5n3vcmlhqfq4vpxreqskxzay6xhdrxx7c38ckqs95v5459uyqqqqyqqtwsqqgqqqqqqqqqqqqqq9gea2fjj7q302ncprk2pawk4zdtayycvm0wtjpprml96h9vujvmqdp0n5z8v7lqk44mq9620jszwaevj0mws7rwd2cegxvlmfszwgpgfqp2xafjf" bHandler.userHasAccess = handlerUserHasAccess - // add a zero amount withdrawal with a time lesser than 2 hours to beat the 1 hour withdrawal timer - paymentTime := time.Now().Add(-time.Hour * 2) - - payment := db.NewPaymentHistory{ - Amount: 0, - WorkspaceUuid: workspace.Uuid, - PaymentType: db.Withdraw, - SenderPubKey: person.OwnerPubKey, - ReceiverPubKey: person.OwnerPubKey, - Tag: "test_withdraw_before_loop", - Status: true, - Created: &paymentTime, - Updated: &paymentTime, - } - - db.TestDB.AddPaymentHistory(payment) - for i := 0; i < 3; i++ { expectedFinalBudget := initialBudget - (paymentAmount * uint(i+1)) mockHttpClient.ExpectedCalls = nil mockHttpClient.Calls = nil + // add a zero amount withdrawal with a time lesser than 2 + loop index hours to beat the 1 hour withdrawal timer + dur := int(time.Hour.Hours())*2 + i + 1 + paymentTime = time.Now().Add(-time.Hour * time.Duration(dur)) + + tag := fmt.Sprintf("test_withdraw:%d", i) + + payment := db.NewPaymentHistory{ + Amount: 0, + WorkspaceUuid: workspace.Uuid, + PaymentType: db.Withdraw, + SenderPubKey: person.OwnerPubKey, + ReceiverPubKey: person.OwnerPubKey, + Tag: tag, + Status: true, + Created: &paymentTime, + Updated: &paymentTime, + } + + db.TestDB.AddPaymentHistory(payment) + + time.Sleep(2 * time.Second) + mockHttpClient.On("Do", mock.AnythingOfType("*http.Request")).Return(&http.Response{ StatusCode: 200, Body: io.NopCloser(bytes.NewBufferString(`{"status": "COMPLETE", "amt_msat": "1000", "timestamp": "" }`)), @@ -2019,25 +2026,6 @@ func TestBountyBudgetWithdraw(t *testing.T) { finalBudget := db.TestDB.GetWorkspaceBudget(workspace.Uuid) assert.Equal(t, expectedFinalBudget, finalBudget.TotalBudget, "The workspace's final budget should reflect the deductions from the successful withdrawals") - // add a zero amount withdrawal with a time lesser than 2 + loop index hours to beat the 1 hour withdrawal timer - dur := int(time.Hour.Hours())*2 + i - paymentTime = time.Now().Add(-time.Hour * time.Duration(dur)) - - tag := fmt.Sprintf("test_withdraw:%d", i) - - payment := db.NewPaymentHistory{ - Amount: 0, - WorkspaceUuid: workspace.Uuid, - PaymentType: db.Withdraw, - SenderPubKey: person.OwnerPubKey, - ReceiverPubKey: person.OwnerPubKey, - Tag: tag, - Status: true, - Created: &paymentTime, - Updated: &paymentTime, - } - - db.TestDB.AddPaymentHistory(payment) } }) From bf8501e5df845b15d00fc8b4ac94f81dad5ad4d8 Mon Sep 17 00:00:00 2001 From: elraphty Date: Fri, 18 Oct 2024 12:31:25 +0100 Subject: [PATCH 11/24] push ater passing locally --- handlers/bounty_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/handlers/bounty_test.go b/handlers/bounty_test.go index 706d44994..52cda7c48 100644 --- a/handlers/bounty_test.go +++ b/handlers/bounty_test.go @@ -1939,7 +1939,7 @@ func TestBountyBudgetWithdraw(t *testing.T) { db.TestDB.AddPaymentHistory(payment) - time.Sleep(2 * time.Second) + time.Sleep(1 * time.Second) mockHttpClient.On("Do", mock.AnythingOfType("*http.Request")).Return(&http.Response{ StatusCode: 400, @@ -2000,7 +2000,7 @@ func TestBountyBudgetWithdraw(t *testing.T) { db.TestDB.AddPaymentHistory(payment) - time.Sleep(2 * time.Second) + time.Sleep(1 * time.Second) mockHttpClient.On("Do", mock.AnythingOfType("*http.Request")).Return(&http.Response{ StatusCode: 200, From 732415430c3c363d47565cec02a6b8d81c3dfd67 Mon Sep 17 00:00:00 2001 From: elraphty Date: Fri, 18 Oct 2024 12:49:53 +0100 Subject: [PATCH 12/24] added to see the workspace_uuid in test --- handlers/bounty.go | 2 +- handlers/bounty_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/handlers/bounty.go b/handlers/bounty.go index 13538a53a..11366c0c0 100644 --- a/handlers/bounty.go +++ b/handlers/bounty.go @@ -940,7 +940,7 @@ func (h *bountyHandler) BountyBudgetWithdraw(w http.ResponseWriter, r *http.Requ if hoursDiff < 1 { w.WriteHeader(http.StatusForbidden) errMsg := formatPayError("Your last withdrawal is not more than an hour ago") - log.Println("Your last withdrawal is not more than an hour ago", hoursDiff, lastWithdrawal.Created) + log.Println("Your last withdrawal is not more than an hour ago", hoursDiff, lastWithdrawal.Created, request.OrgUuid) json.NewEncoder(w).Encode(errMsg) h.m.Unlock() return diff --git a/handlers/bounty_test.go b/handlers/bounty_test.go index 52cda7c48..c9f918db1 100644 --- a/handlers/bounty_test.go +++ b/handlers/bounty_test.go @@ -1787,7 +1787,7 @@ func TestBountyBudgetWithdraw(t *testing.T) { budgetAmount := uint(5000) - paymentTime := time.Now() + paymentTime := time.Now().Add(-time.Hour * 2) payment := db.NewPaymentHistory{ Amount: budgetAmount, From 92739a44cc1a0b9a0139798fa19532e1e1e58da2 Mon Sep 17 00:00:00 2001 From: elraphty Date: Fri, 18 Oct 2024 13:13:23 +0100 Subject: [PATCH 13/24] add ageneral late time --- handlers/bounty_test.go | 58 ++++++++++++++--------------------------- 1 file changed, 20 insertions(+), 38 deletions(-) diff --git a/handlers/bounty_test.go b/handlers/bounty_test.go index c9f918db1..3e6200588 100644 --- a/handlers/bounty_test.go +++ b/handlers/bounty_test.go @@ -1787,7 +1787,7 @@ func TestBountyBudgetWithdraw(t *testing.T) { budgetAmount := uint(5000) - paymentTime := time.Now().Add(-time.Hour * 2) + paymentTime := time.Now() payment := db.NewPaymentHistory{ Amount: budgetAmount, @@ -1803,6 +1803,25 @@ func TestBountyBudgetWithdraw(t *testing.T) { db.TestDB.AddPaymentHistory(payment) + // add a zero amount withdrawal with a time lesser than 2 hours to beat the 1 hour withdrawal timer + paymentWTime := time.Now().Add(-time.Hour * 2) + + payment = db.NewPaymentHistory{ + Amount: 0, + WorkspaceUuid: workspace.Uuid, + PaymentType: db.Withdraw, + SenderPubKey: person.OwnerPubKey, + ReceiverPubKey: person.OwnerPubKey, + Tag: "test_withdraw__time", + Status: true, + Created: &paymentWTime, + Updated: &paymentWTime, + } + + db.TestDB.AddPaymentHistory(payment) + + time.Sleep(1 * time.Second) + budget := db.NewBountyBudget{ WorkspaceUuid: workspace.Uuid, TotalBudget: budgetAmount, @@ -1922,25 +1941,6 @@ func TestBountyBudgetWithdraw(t *testing.T) { }) t.Run("400 BadRequest error if there is an error with invoice payment", func(t *testing.T) { - // add a zero amount withdrawal with a time lesser than 2 hours to beat the 1 hour withdrawal timer - paymentTime := time.Now().Add(-time.Hour * 2) - - payment := db.NewPaymentHistory{ - Amount: 0, - WorkspaceUuid: workspace.Uuid, - PaymentType: db.Withdraw, - SenderPubKey: person.OwnerPubKey, - ReceiverPubKey: person.OwnerPubKey, - Tag: "test_withdraw_before_400_error", - Status: true, - Created: &paymentTime, - Updated: &paymentTime, - } - - db.TestDB.AddPaymentHistory(payment) - - time.Sleep(1 * time.Second) - mockHttpClient.On("Do", mock.AnythingOfType("*http.Request")).Return(&http.Response{ StatusCode: 400, Body: io.NopCloser(bytes.NewBufferString(`{"success": false, "error": "Payment error"}`)), @@ -1984,24 +1984,6 @@ func TestBountyBudgetWithdraw(t *testing.T) { dur := int(time.Hour.Hours())*2 + i + 1 paymentTime = time.Now().Add(-time.Hour * time.Duration(dur)) - tag := fmt.Sprintf("test_withdraw:%d", i) - - payment := db.NewPaymentHistory{ - Amount: 0, - WorkspaceUuid: workspace.Uuid, - PaymentType: db.Withdraw, - SenderPubKey: person.OwnerPubKey, - ReceiverPubKey: person.OwnerPubKey, - Tag: tag, - Status: true, - Created: &paymentTime, - Updated: &paymentTime, - } - - db.TestDB.AddPaymentHistory(payment) - - time.Sleep(1 * time.Second) - mockHttpClient.On("Do", mock.AnythingOfType("*http.Request")).Return(&http.Response{ StatusCode: 200, Body: io.NopCloser(bytes.NewBufferString(`{"status": "COMPLETE", "amt_msat": "1000", "timestamp": "" }`)), From 27b9e5e14766bb70f910d0ec3215cc599e9a4b0f Mon Sep 17 00:00:00 2001 From: elraphty Date: Fri, 18 Oct 2024 13:25:46 +0100 Subject: [PATCH 14/24] add general greater time to fix --- handlers/bounty_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handlers/bounty_test.go b/handlers/bounty_test.go index 3e6200588..c6b1edec5 100644 --- a/handlers/bounty_test.go +++ b/handlers/bounty_test.go @@ -1804,7 +1804,7 @@ func TestBountyBudgetWithdraw(t *testing.T) { db.TestDB.AddPaymentHistory(payment) // add a zero amount withdrawal with a time lesser than 2 hours to beat the 1 hour withdrawal timer - paymentWTime := time.Now().Add(-time.Hour * 2) + paymentWTime := time.Now().Add(time.Hour * 3) payment = db.NewPaymentHistory{ Amount: 0, From c9d482f5815a321d8129d6c699a83131328efa25 Mon Sep 17 00:00:00 2001 From: elraphty Date: Fri, 18 Oct 2024 13:36:54 +0100 Subject: [PATCH 15/24] revert after testing positive time additions --- handlers/bounty_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handlers/bounty_test.go b/handlers/bounty_test.go index c6b1edec5..b6fbdbc81 100644 --- a/handlers/bounty_test.go +++ b/handlers/bounty_test.go @@ -1804,7 +1804,7 @@ func TestBountyBudgetWithdraw(t *testing.T) { db.TestDB.AddPaymentHistory(payment) // add a zero amount withdrawal with a time lesser than 2 hours to beat the 1 hour withdrawal timer - paymentWTime := time.Now().Add(time.Hour * 3) + paymentWTime := time.Now().Add(-time.Hour * 3) payment = db.NewPaymentHistory{ Amount: 0, From ed38f222e55dca3af9a1f2c3a06481020294a5d0 Mon Sep 17 00:00:00 2001 From: elraphty Date: Fri, 18 Oct 2024 13:59:34 +0100 Subject: [PATCH 16/24] revert after testing positive time additions --- handlers/bounty.go | 6 ++++-- handlers/bounty_test.go | 26 +++++++------------------- 2 files changed, 11 insertions(+), 21 deletions(-) diff --git a/handlers/bounty.go b/handlers/bounty.go index 11366c0c0..3cc66dcfc 100644 --- a/handlers/bounty.go +++ b/handlers/bounty.go @@ -26,6 +26,7 @@ type bountyHandler struct { generateBountyResponse func(bounties []db.NewBounty) []db.BountyResponse userHasAccess func(pubKeyFromAuth string, uuid string, role string) bool getInvoiceStatusByTag func(tag string) db.V2TagRes + getHoursDifference func(createdDate int64, endDate *time.Time) int64 userHasManageBountyRoles func(pubKeyFromAuth string, uuid string) bool m sync.Mutex } @@ -39,6 +40,7 @@ func NewBountyHandler(httpClient HttpClient, database db.Database) *bountyHandle getSocketConnections: db.Store.GetSocketConnections, userHasAccess: dbConf.UserHasAccess, getInvoiceStatusByTag: GetInvoiceStatusByTag, + getHoursDifference: utils.GetHoursDifference, userHasManageBountyRoles: dbConf.UserHasManageBountyRoles, } } @@ -930,10 +932,10 @@ func (h *bountyHandler) BountyBudgetWithdraw(w http.ResponseWriter, r *http.Requ lastWithdrawal := h.db.GetLastWithdrawal(request.OrgUuid) if lastWithdrawal.ID > 0 { - now := time.Now() withdrawCreated := lastWithdrawal.Created withdrawTime := utils.ConvertTimeToTimestamp(withdrawCreated.String()) + hoursDiff := utils.GetHoursDifference(int64(withdrawTime), &now) // Check that last withdraw time is greater than 1 @@ -1044,7 +1046,7 @@ func (h *bountyHandler) NewBountyBudgetWithdraw(w http.ResponseWriter, r *http.R now := time.Now() withdrawCreated := lastWithdrawal.Created withdrawTime := utils.ConvertTimeToTimestamp(withdrawCreated.String()) - hoursDiff := utils.GetHoursDifference(int64(withdrawTime), &now) + hoursDiff := h.getHoursDifference(int64(withdrawTime), &now) // Check that last withdraw time is greater than 1 if hoursDiff < 1 { diff --git a/handlers/bounty_test.go b/handlers/bounty_test.go index b6fbdbc81..054b5a53d 100644 --- a/handlers/bounty_test.go +++ b/handlers/bounty_test.go @@ -1765,6 +1765,10 @@ func TestBountyBudgetWithdraw(t *testing.T) { return false } + getHoursDifference := func(createdDate int64, endDate *time.Time) int64 { + return 2 + } + person := db.Person{ Uuid: uuid.New().String(), OwnerAlias: "test-alias", @@ -1803,25 +1807,6 @@ func TestBountyBudgetWithdraw(t *testing.T) { db.TestDB.AddPaymentHistory(payment) - // add a zero amount withdrawal with a time lesser than 2 hours to beat the 1 hour withdrawal timer - paymentWTime := time.Now().Add(-time.Hour * 3) - - payment = db.NewPaymentHistory{ - Amount: 0, - WorkspaceUuid: workspace.Uuid, - PaymentType: db.Withdraw, - SenderPubKey: person.OwnerPubKey, - ReceiverPubKey: person.OwnerPubKey, - Tag: "test_withdraw__time", - Status: true, - Created: &paymentWTime, - Updated: &paymentWTime, - } - - db.TestDB.AddPaymentHistory(payment) - - time.Sleep(1 * time.Second) - budget := db.NewBountyBudget{ WorkspaceUuid: workspace.Uuid, TotalBudget: budgetAmount, @@ -1941,6 +1926,8 @@ func TestBountyBudgetWithdraw(t *testing.T) { }) t.Run("400 BadRequest error if there is an error with invoice payment", func(t *testing.T) { + bHandler.getHoursDifference = getHoursDifference + mockHttpClient.On("Do", mock.AnythingOfType("*http.Request")).Return(&http.Response{ StatusCode: 400, Body: io.NopCloser(bytes.NewBufferString(`{"success": false, "error": "Payment error"}`)), @@ -1969,6 +1956,7 @@ func TestBountyBudgetWithdraw(t *testing.T) { }) t.Run("Should test that an Workspace's Budget Total Amount is accurate after three (3) successful 'Budget Withdrawal Requests'", func(t *testing.T) { + bHandler.getHoursDifference = getHoursDifference paymentAmount := uint(1000) initialBudget := budget.TotalBudget From f0bf975c399b616aab98b1a7c1d776e6de549ed2 Mon Sep 17 00:00:00 2001 From: elraphty Date: Fri, 18 Oct 2024 14:20:02 +0100 Subject: [PATCH 17/24] mocked getHoursDifference --- handlers/bounty_test.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/handlers/bounty_test.go b/handlers/bounty_test.go index 054b5a53d..d2a466eeb 100644 --- a/handlers/bounty_test.go +++ b/handlers/bounty_test.go @@ -1757,6 +1757,7 @@ func TestBountyBudgetWithdraw(t *testing.T) { ctx := context.Background() mockHttpClient := mocks.NewHttpClient(t) bHandler := NewBountyHandler(mockHttpClient, db.TestDB) + handlerUserHasAccess := func(pubKeyFromAuth string, uuid string, role string) bool { return true } @@ -1956,12 +1957,12 @@ func TestBountyBudgetWithdraw(t *testing.T) { }) t.Run("Should test that an Workspace's Budget Total Amount is accurate after three (3) successful 'Budget Withdrawal Requests'", func(t *testing.T) { - bHandler.getHoursDifference = getHoursDifference - paymentAmount := uint(1000) initialBudget := budget.TotalBudget invoice := "lnbcrt10u1pnv7nz6dqld9h8vmmfvdjjqen0wgsrzvpsxqcrqvqpp54v0synj4q3j2usthzt8g5umteky6d2apvgtaxd7wkepkygxgqdyssp5lhv2878qjas3azv3nnu8r6g3tlgejl7mu7cjzc9q5haygrpapd4s9qrsgqcqpjxqrrssrzjqgtzc5n3vcmlhqfq4vpxreqskxzay6xhdrxx7c38ckqs95v5459uyqqqqyqqtwsqqgqqqqqqqqqqqqqq9gea2fjj7q302ncprk2pawk4zdtayycvm0wtjpprml96h9vujvmqdp0n5z8v7lqk44mq9620jszwaevj0mws7rwd2cegxvlmfszwgpgfqp2xafjf" + bHandler.userHasAccess = handlerUserHasAccess + bHandler.getHoursDifference = getHoursDifference for i := 0; i < 3; i++ { expectedFinalBudget := initialBudget - (paymentAmount * uint(i+1)) From fb21888f39b94599d8a681b929994dc550dacde2 Mon Sep 17 00:00:00 2001 From: elraphty Date: Fri, 18 Oct 2024 14:37:37 +0100 Subject: [PATCH 18/24] mocked getHours for withdraw bounty --- db/test_config.go | 6 +++--- handlers/bounty.go | 3 +-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/db/test_config.go b/db/test_config.go index 4416b8b55..0e6131b27 100644 --- a/db/test_config.go +++ b/db/test_config.go @@ -12,10 +12,10 @@ var TestDB database func InitTestDB() { rdsHost := "localhost" - rdsPort := fmt.Sprintf("%d", 5532) + rdsPort := fmt.Sprintf("%d", 5432) rdsDbName := "test_db" - rdsUsername := "test_user" - rdsPassword := "test_password" + rdsUsername := "raph" + rdsPassword := "raph" dbURL := fmt.Sprintf("postgres://%s:%s@%s:%s/%s", rdsUsername, rdsPassword, rdsHost, rdsPort, rdsDbName) if dbURL == "" { diff --git a/handlers/bounty.go b/handlers/bounty.go index 3cc66dcfc..77fb66b13 100644 --- a/handlers/bounty.go +++ b/handlers/bounty.go @@ -34,7 +34,6 @@ type bountyHandler struct { func NewBountyHandler(httpClient HttpClient, database db.Database) *bountyHandler { dbConf := db.NewDatabaseConfig(&gorm.DB{}) return &bountyHandler{ - httpClient: httpClient, db: database, getSocketConnections: db.Store.GetSocketConnections, @@ -936,7 +935,7 @@ func (h *bountyHandler) BountyBudgetWithdraw(w http.ResponseWriter, r *http.Requ withdrawCreated := lastWithdrawal.Created withdrawTime := utils.ConvertTimeToTimestamp(withdrawCreated.String()) - hoursDiff := utils.GetHoursDifference(int64(withdrawTime), &now) + hoursDiff := h.getHoursDifference(int64(withdrawTime), &now) // Check that last withdraw time is greater than 1 if hoursDiff < 1 { From 4803f35a9269d959c4fd99aa32ffcd2153beaa9a Mon Sep 17 00:00:00 2001 From: elraphty Date: Fri, 18 Oct 2024 14:44:55 +0100 Subject: [PATCH 19/24] revert to default test config --- db/test_config.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/db/test_config.go b/db/test_config.go index 0e6131b27..4416b8b55 100644 --- a/db/test_config.go +++ b/db/test_config.go @@ -12,10 +12,10 @@ var TestDB database func InitTestDB() { rdsHost := "localhost" - rdsPort := fmt.Sprintf("%d", 5432) + rdsPort := fmt.Sprintf("%d", 5532) rdsDbName := "test_db" - rdsUsername := "raph" - rdsPassword := "raph" + rdsUsername := "test_user" + rdsPassword := "test_password" dbURL := fmt.Sprintf("postgres://%s:%s@%s:%s/%s", rdsUsername, rdsPassword, rdsHost, rdsPort, rdsDbName) if dbURL == "" { From f4e1988a31163528ef44b6cf40d621cf962ef1fc Mon Sep 17 00:00:00 2001 From: elraphty Date: Fri, 18 Oct 2024 17:02:52 +0100 Subject: [PATCH 20/24] after security checks --- db/interface.go | 2 +- db/test_config.go | 6 +- db/workspaces.go | 21 ++++++- handlers/bounty.go | 130 +++++----------------------------------- handlers/bounty_test.go | 18 +++--- handlers/workspaces.go | 12 ++-- mocks/Database.go | 21 +++---- routes/bounty.go | 1 - 8 files changed, 66 insertions(+), 145 deletions(-) diff --git a/db/interface.go b/db/interface.go index d3473f6bd..d3d621e8a 100644 --- a/db/interface.go +++ b/db/interface.go @@ -106,7 +106,7 @@ type Database interface { GetWorkspaceBudget(workspace_uuid string) NewBountyBudget GetWorkspaceStatusBudget(workspace_uuid string) StatusBudget GetWorkspaceBudgetHistory(workspace_uuid string) []BudgetHistoryData - ProcessUpdateBudget(invoice NewInvoiceList) error + ProcessUpdateBudget(invoice NewInvoiceList, getLightningInvoice func(payment_request string) (InvoiceResult, InvoiceError)) error AddAndUpdateBudget(invoice NewInvoiceList) NewPaymentHistory WithdrawBudget(sender_pubkey string, workspace_uuid string, amount uint) AddPaymentHistory(payment NewPaymentHistory) NewPaymentHistory diff --git a/db/test_config.go b/db/test_config.go index 4416b8b55..0e6131b27 100644 --- a/db/test_config.go +++ b/db/test_config.go @@ -12,10 +12,10 @@ var TestDB database func InitTestDB() { rdsHost := "localhost" - rdsPort := fmt.Sprintf("%d", 5532) + rdsPort := fmt.Sprintf("%d", 5432) rdsDbName := "test_db" - rdsUsername := "test_user" - rdsPassword := "test_password" + rdsUsername := "raph" + rdsPassword := "raph" dbURL := fmt.Sprintf("postgres://%s:%s@%s:%s/%s", rdsUsername, rdsPassword, rdsHost, rdsPort, rdsDbName) if dbURL == "" { diff --git a/db/workspaces.go b/db/workspaces.go index 767ffa942..dade43e18 100644 --- a/db/workspaces.go +++ b/db/workspaces.go @@ -264,7 +264,7 @@ func (db database) GetWorkspaceBudgetHistory(workspace_uuid string) []BudgetHist return budgetHistory } -func (db database) ProcessUpdateBudget(invoice NewInvoiceList) error { +func (db database) ProcessUpdateBudget(invoice NewInvoiceList, getLightningInvoice func(payment_request string) (InvoiceResult, InvoiceError)) error { // Start db transaction tx := db.db.Begin() @@ -283,6 +283,25 @@ func (db database) ProcessUpdateBudget(invoice NewInvoiceList) error { created := invoice.Created workspace_uuid := invoice.WorkspaceUuid + if invoice.Status { + tx.Rollback() + return errors.New("cannot process already paid invoice") + } + + invoiceRes, invoiceErr := getLightningInvoice(invoice.PaymentRequest) + + if invoiceErr.Error != "" { + tx.Rollback() + + return errors.New("could not check invoice") + } + + if !invoiceRes.Response.Settled { + tx.Rollback() + + return errors.New("invoice has not been settled") + } + if workspace_uuid == "" { return errors.New("cannot Create a Workspace Without a Workspace uuid") } diff --git a/handlers/bounty.go b/handlers/bounty.go index 77fb66b13..2c2077410 100644 --- a/handlers/bounty.go +++ b/handlers/bounty.go @@ -898,6 +898,7 @@ func (h *bountyHandler) UpdateBountyPaymentStatus(w http.ResponseWriter, r *http json.NewEncoder(w).Encode(msg) } +// Todo: change back to BountyBudgetWithdraw func (h *bountyHandler) BountyBudgetWithdraw(w http.ResponseWriter, r *http.Request) { h.m.Lock() @@ -911,7 +912,7 @@ func (h *bountyHandler) BountyBudgetWithdraw(w http.ResponseWriter, r *http.Requ return } - request := db.WithdrawBudgetRequest{} + request := db.NewWithdrawBudgetRequest{} body, err := io.ReadAll(r.Body) r.Body.Close() @@ -928,7 +929,7 @@ func (h *bountyHandler) BountyBudgetWithdraw(w http.ResponseWriter, r *http.Requ return } - lastWithdrawal := h.db.GetLastWithdrawal(request.OrgUuid) + lastWithdrawal := h.db.GetLastWithdrawal(request.WorkspaceUuid) if lastWithdrawal.ID > 0 { now := time.Now() @@ -939,20 +940,20 @@ func (h *bountyHandler) BountyBudgetWithdraw(w http.ResponseWriter, r *http.Requ // Check that last withdraw time is greater than 1 if hoursDiff < 1 { - w.WriteHeader(http.StatusForbidden) - errMsg := formatPayError("Your last withdrawal is not more than an hour ago") - log.Println("Your last withdrawal is not more than an hour ago", hoursDiff, lastWithdrawal.Created, request.OrgUuid) + w.WriteHeader(http.StatusUnauthorized) + errMsg := formatPayError("Your last withdrawal is not more than an hour ago") + log.Println("Your last withdrawal is not more than an hour ago", hoursDiff, lastWithdrawal.Created, request.WorkspaceUuid) json.NewEncoder(w).Encode(errMsg) h.m.Unlock() return } } - log.Printf("[bounty] [BountyBudgetWithdraw] Logging body: workspace_uuid: %s, pubkey: %s, invoice: %s", request.OrgUuid, pubKeyFromAuth, request.PaymentRequest) + log.Printf("[bounty] [BountyBudgetWithdraw] Logging body: workspace_uuid: %s, pubkey: %s, invoice: %s", request.WorkspaceUuid, pubKeyFromAuth, request.PaymentRequest) // check if user is the admin of the workspace // or has a withdraw bounty budget role - hasRole := h.userHasAccess(pubKeyFromAuth, request.OrgUuid, db.WithdrawBudget) + hasRole := h.userHasAccess(pubKeyFromAuth, request.WorkspaceUuid, db.WithdrawBudget) if !hasRole { w.WriteHeader(http.StatusUnauthorized) errMsg := formatPayError("You don't have appropriate permissions to withdraw bounty budget") @@ -966,7 +967,7 @@ func (h *bountyHandler) BountyBudgetWithdraw(w http.ResponseWriter, r *http.Requ if amount > 0 { // check if the workspace bounty balance // is greater than the amount - orgBudget := h.db.GetWorkspaceBudget(request.OrgUuid) + orgBudget := h.db.GetWorkspaceBudget(request.WorkspaceUuid) if amount > orgBudget.TotalBudget { w.WriteHeader(http.StatusForbidden) errMsg := formatPayError("Workspace budget is not enough to withdraw the amount") @@ -975,103 +976,6 @@ func (h *bountyHandler) BountyBudgetWithdraw(w http.ResponseWriter, r *http.Requ return } - // Check that the deposit is more than the withdrawal plus amount to withdraw - sumOfWithdrawals := h.db.GetSumOfWithdrawal(request.OrgUuid) - sumOfDeposits := h.db.GetSumOfDeposits(request.OrgUuid) - - if sumOfDeposits < sumOfWithdrawals+amount { - w.WriteHeader(http.StatusForbidden) - - log.Printf("Sum of deposits is lesser than withdrawal: %d : %d", sumOfDeposits, sumOfWithdrawals) - errMsg := formatPayError("Your deposits is lesser than your withdrawal") - json.NewEncoder(w).Encode(errMsg) - h.m.Unlock() - return - } - - paymentSuccess, paymentError := h.PayLightningInvoice(request.PaymentRequest) - if paymentSuccess.Success { - // withdraw amount from workspace budget - h.db.WithdrawBudget(pubKeyFromAuth, request.OrgUuid, amount) - w.WriteHeader(http.StatusOK) - json.NewEncoder(w).Encode(paymentSuccess) - } else { - log.Printf("Withdrawal payment failed: %s ", paymentError.Error) - w.WriteHeader(http.StatusBadRequest) - json.NewEncoder(w).Encode(paymentError) - } - } else { - w.WriteHeader(http.StatusForbidden) - errMsg := formatPayError("Could not pay lightning invoice") - json.NewEncoder(w).Encode(errMsg) - } - - h.m.Unlock() -} - -// Todo: change back to NewBountyBudgetWithdraw -func (h *bountyHandler) NewBountyBudgetWithdraw(w http.ResponseWriter, r *http.Request) { - h.m.Lock() - - ctx := r.Context() - pubKeyFromAuth, _ := ctx.Value(auth.ContextKey).(string) - - if pubKeyFromAuth == "" { - fmt.Println("[bounty] no pubkey from auth") - w.WriteHeader(http.StatusUnauthorized) - h.m.Unlock() - return - } - - request := db.NewWithdrawBudgetRequest{} - body, err := io.ReadAll(r.Body) - r.Body.Close() - - if err != nil { - w.WriteHeader(http.StatusNotAcceptable) - h.m.Unlock() - return - } - - err = json.Unmarshal(body, &request) - if err != nil { - w.WriteHeader(http.StatusNotAcceptable) - h.m.Unlock() - return - } - - lastWithdrawal := h.db.GetLastWithdrawal(request.WorkspaceUuid) - - now := time.Now() - withdrawCreated := lastWithdrawal.Created - withdrawTime := utils.ConvertTimeToTimestamp(withdrawCreated.String()) - hoursDiff := h.getHoursDifference(int64(withdrawTime), &now) - - // Check that last withdraw time is greater than 1 - if hoursDiff < 1 { - w.WriteHeader(http.StatusUnauthorized) - errMsg := formatPayError("Your last withdrawal is not more than an hour ago") - json.NewEncoder(w).Encode(errMsg) - h.m.Unlock() - return - } - - log.Printf("[bounty] [BountyBudgetWithdraw] Logging body: workspace_uuid: %s, pubkey: %s, invoice: %s", request.WorkspaceUuid, pubKeyFromAuth, request.PaymentRequest) - - // check if user is the admin of the workspace - // or has a withdraw bounty budget role - hasRole := h.userHasAccess(pubKeyFromAuth, request.WorkspaceUuid, db.WithdrawBudget) - if !hasRole { - w.WriteHeader(http.StatusUnauthorized) - errMsg := formatPayError("You don't have appropriate permissions to withdraw bounty budget") - json.NewEncoder(w).Encode(errMsg) - h.m.Unlock() - return - } - - amount := utils.GetInvoiceAmount(request.PaymentRequest) - - if amount > 0 { // Check that the deposit is more than the withdrawal plus amount to withdraw sumOfWithdrawals := h.db.GetSumOfWithdrawal(request.WorkspaceUuid) sumOfDeposits := h.db.GetSumOfDeposits(request.WorkspaceUuid) @@ -1084,16 +988,6 @@ func (h *bountyHandler) NewBountyBudgetWithdraw(w http.ResponseWriter, r *http.R return } - // check if the workspace bounty balance - // is greater than the amount - orgBudget := h.db.GetWorkspaceBudget(request.WorkspaceUuid) - if amount > orgBudget.TotalBudget { - w.WriteHeader(http.StatusForbidden) - errMsg := formatPayError("Workspace budget is not enough to withdraw the amount") - json.NewEncoder(w).Encode(errMsg) - h.m.Unlock() - return - } paymentSuccess, paymentError := h.PayLightningInvoice(request.PaymentRequest) if paymentSuccess.Success { // withdraw amount from workspace budget @@ -1387,6 +1281,8 @@ func (h *bountyHandler) GetInvoiceData(w http.ResponseWriter, r *http.Request) { } func (h *bountyHandler) PollInvoice(w http.ResponseWriter, r *http.Request) { + h.m.Lock() + ctx := r.Context() pubKeyFromAuth, _ := ctx.Value(auth.ContextKey).(string) paymentRequest := chi.URLParam(r, "paymentRequest") @@ -1395,6 +1291,7 @@ func (h *bountyHandler) PollInvoice(w http.ResponseWriter, r *http.Request) { if pubKeyFromAuth == "" { fmt.Println("[bounty] no pubkey from auth") w.WriteHeader(http.StatusUnauthorized) + h.m.Unlock() return } @@ -1403,6 +1300,7 @@ func (h *bountyHandler) PollInvoice(w http.ResponseWriter, r *http.Request) { if invoiceErr.Error != "" { w.WriteHeader(http.StatusForbidden) json.NewEncoder(w).Encode(invoiceErr) + h.m.Unlock() return } @@ -1537,6 +1435,8 @@ func (h *bountyHandler) PollInvoice(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(invoiceRes) + + h.m.Unlock() } func GetFilterCount(w http.ResponseWriter, r *http.Request) { diff --git a/handlers/bounty_test.go b/handlers/bounty_test.go index d2a466eeb..2f36d8345 100644 --- a/handlers/bounty_test.go +++ b/handlers/bounty_test.go @@ -1851,7 +1851,7 @@ func TestBountyBudgetWithdraw(t *testing.T) { rr := httptest.NewRecorder() handler := http.HandlerFunc(bHandler.BountyBudgetWithdraw) - validData := []byte(`{"orgUuid": "workspace-uuid", "paymentRequest": "invoice"}`) + validData := []byte(`{"workspace_uuid": "workspace-uuid", "paymentRequest": "invoice"}`) req, err := http.NewRequestWithContext(authorizedCtx, http.MethodPost, "/budget/withdraw", bytes.NewReader(validData)) if err != nil { t.Fatal(err) @@ -1872,9 +1872,9 @@ func TestBountyBudgetWithdraw(t *testing.T) { amount := utils.GetInvoiceAmount(invoice) assert.Equal(t, uint(10000), amount) - withdrawRequest := db.WithdrawBudgetRequest{ + withdrawRequest := db.NewWithdrawBudgetRequest{ PaymentRequest: invoice, - OrgUuid: workspace.Uuid, + WorkspaceUuid: workspace.Uuid, } requestBody, _ := json.Marshal(withdrawRequest) req, _ := http.NewRequestWithContext(authorizedCtx, http.MethodPost, "/budget/withdraw", bytes.NewReader(requestBody)) @@ -1906,9 +1906,9 @@ func TestBountyBudgetWithdraw(t *testing.T) { invoice := "lnbc3u1pngsqv8pp5vl6ep8llmg3f9sfu8j7ctcnphylpnjduuyljqf3sc30z6ejmrunqdqzvscqzpgxqyz5vqrzjqwnw5tv745sjpvft6e3f9w62xqk826vrm3zaev4nvj6xr3n065aukqqqqyqqz9gqqyqqqqqqqqqqqqqqqqsp5n9hrrw6pr89qn3c82vvhy697wp45zdsyhm7tnu536ga77ytvxxaq9qrssqqqhenjtquz8wz5tym8v830h9gjezynjsazystzj6muhw4rd9ccc40p8sazjuk77hhcj0xn72lfyee3tsfl7lucxkx5xgtfaqya9qldcqr3072z" - withdrawRequest := db.WithdrawBudgetRequest{ + withdrawRequest := db.NewWithdrawBudgetRequest{ PaymentRequest: invoice, - OrgUuid: workspace.Uuid, + WorkspaceUuid: workspace.Uuid, } requestBody, _ := json.Marshal(withdrawRequest) @@ -1936,9 +1936,9 @@ func TestBountyBudgetWithdraw(t *testing.T) { invoice := "lnbcrt1u1pnv5ejzdqad9h8vmmfvdjjqen0wgsrzvpsxqcrqpp58xyhvymlhc8q05z930fknk2vdl8wnpm5zlx5lgp4ev9u8h7yd4kssp5nu652c5y0epuxeawn8szcgdrjxwk7pfkdh9tsu44r7hacg52nfgq9qrsgqcqpjxqrrssrzjqgtzc5n3vcmlhqfq4vpxreqskxzay6xhdrxx7c38ckqs95v5459uyqqqqyqq9ggqqsqqqqqqqqqqqqqq9gwyffzjpnrwt6yswwd4znt2xqnwjwxgq63qxudru95a8pqeer2r7sduurtstz5x60y4e7m4y9nx6rqy5sr9k08vtwv6s37xh0z5pdwpgqxeqdtv" - withdrawRequest := db.WithdrawBudgetRequest{ + withdrawRequest := db.NewWithdrawBudgetRequest{ PaymentRequest: invoice, - OrgUuid: workspace.Uuid, + WorkspaceUuid: workspace.Uuid, } requestBody, _ := json.Marshal(withdrawRequest) req, _ := http.NewRequestWithContext(authorizedCtx, http.MethodPost, "/budget/withdraw", bytes.NewReader(requestBody)) @@ -1978,9 +1978,9 @@ func TestBountyBudgetWithdraw(t *testing.T) { Body: io.NopCloser(bytes.NewBufferString(`{"status": "COMPLETE", "amt_msat": "1000", "timestamp": "" }`)), }, nil) - withdrawRequest := db.WithdrawBudgetRequest{ + withdrawRequest := db.NewWithdrawBudgetRequest{ PaymentRequest: invoice, - OrgUuid: workspace.Uuid, + WorkspaceUuid: workspace.Uuid, } requestBody, _ := json.Marshal(withdrawRequest) req, _ := http.NewRequestWithContext(authorizedCtx, http.MethodPost, "/budget/withdraw", bytes.NewReader(requestBody)) diff --git a/handlers/workspaces.go b/handlers/workspaces.go index f6e60c2b7..6db71cf36 100644 --- a/handlers/workspaces.go +++ b/handlers/workspaces.go @@ -684,7 +684,7 @@ func (oh *workspaceHandler) PollBudgetInvoices(w http.ResponseWriter, r *http.Re if invoiceRes.Response.Settled { if !inv.Status && inv.Type == "BUDGET" { - oh.db.ProcessUpdateBudget(inv) + oh.db.ProcessUpdateBudget(inv, oh.getLightningInvoice) } } else { // Cheeck if time has expired @@ -729,7 +729,7 @@ func (oh *workspaceHandler) PollUserWorkspacesBudget(w http.ResponseWriter, r *h if invoiceRes.Response.Settled { if !inv.Status && inv.Type == "BUDGET" { - oh.db.ProcessUpdateBudget(inv) + oh.db.ProcessUpdateBudget(inv, oh.getLightningInvoice) } } else { // Cheeck if time has expired @@ -1032,9 +1032,11 @@ func (oh *workspaceHandler) GetLastWithdrawal(w http.ResponseWriter, r *http.Req workspace_uuid := chi.URLParam(r, "workspace_uuid") lastWithdrawal := oh.db.GetLastWithdrawal(workspace_uuid) - now := time.Now().Unix() - withdrawTime := lastWithdrawal.Created - hoursDiff := utils.GetHoursDifference(now, withdrawTime) + now := time.Now() + withdrawCreated := lastWithdrawal.Created + withdrawTime := utils.ConvertTimeToTimestamp(withdrawCreated.String()) + + hoursDiff := utils.GetHoursDifference(int64(withdrawTime), &now) w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(hoursDiff) diff --git a/mocks/Database.go b/mocks/Database.go index 4c40aeb6b..4e0bf60f8 100644 --- a/mocks/Database.go +++ b/mocks/Database.go @@ -6823,17 +6823,17 @@ func (_c *Database_ProcessDeleteWorkspace_Call) RunAndReturn(run func(string) er return _c } -// ProcessUpdateBudget provides a mock function with given fields: invoice -func (_m *Database) ProcessUpdateBudget(invoice db.NewInvoiceList) error { - ret := _m.Called(invoice) +// ProcessUpdateBudget provides a mock function with given fields: invoice, getLightningInvoice +func (_m *Database) ProcessUpdateBudget(invoice db.NewInvoiceList, getLightningInvoice func(string) (db.InvoiceResult, db.InvoiceError)) error { + ret := _m.Called(invoice, getLightningInvoice) if len(ret) == 0 { panic("no return value specified for ProcessUpdateBudget") } var r0 error - if rf, ok := ret.Get(0).(func(db.NewInvoiceList) error); ok { - r0 = rf(invoice) + if rf, ok := ret.Get(0).(func(db.NewInvoiceList, func(string) (db.InvoiceResult, db.InvoiceError)) error); ok { + r0 = rf(invoice, getLightningInvoice) } else { r0 = ret.Error(0) } @@ -6848,13 +6848,14 @@ type Database_ProcessUpdateBudget_Call struct { // ProcessUpdateBudget is a helper method to define mock.On call // - invoice db.NewInvoiceList -func (_e *Database_Expecter) ProcessUpdateBudget(invoice interface{}) *Database_ProcessUpdateBudget_Call { - return &Database_ProcessUpdateBudget_Call{Call: _e.mock.On("ProcessUpdateBudget", invoice)} +// - getLightningInvoice func(string)(db.InvoiceResult , db.InvoiceError) +func (_e *Database_Expecter) ProcessUpdateBudget(invoice interface{}, getLightningInvoice interface{}) *Database_ProcessUpdateBudget_Call { + return &Database_ProcessUpdateBudget_Call{Call: _e.mock.On("ProcessUpdateBudget", invoice, getLightningInvoice)} } -func (_c *Database_ProcessUpdateBudget_Call) Run(run func(invoice db.NewInvoiceList)) *Database_ProcessUpdateBudget_Call { +func (_c *Database_ProcessUpdateBudget_Call) Run(run func(invoice db.NewInvoiceList, getLightningInvoice func(string) (db.InvoiceResult, db.InvoiceError))) *Database_ProcessUpdateBudget_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(db.NewInvoiceList)) + run(args[0].(db.NewInvoiceList), args[1].(func(string) (db.InvoiceResult, db.InvoiceError))) }) return _c } @@ -6864,7 +6865,7 @@ func (_c *Database_ProcessUpdateBudget_Call) Return(_a0 error) *Database_Process return _c } -func (_c *Database_ProcessUpdateBudget_Call) RunAndReturn(run func(db.NewInvoiceList) error) *Database_ProcessUpdateBudget_Call { +func (_c *Database_ProcessUpdateBudget_Call) RunAndReturn(run func(db.NewInvoiceList, func(string) (db.InvoiceResult, db.InvoiceError)) error) *Database_ProcessUpdateBudget_Call { _c.Call.Return(run) return _c } diff --git a/routes/bounty.go b/routes/bounty.go index 639a22c44..d6d83d2e0 100644 --- a/routes/bounty.go +++ b/routes/bounty.go @@ -38,7 +38,6 @@ func BountyRoutes() chi.Router { r.Post("/pay/{id}", bountyHandler.MakeBountyPayment) r.Get("/payment/status/{id}", bountyHandler.GetBountyPaymentStatus) r.Put("/payment/status/{id}", bountyHandler.UpdateBountyPaymentStatus) - r.Post("/budget_workspace/withdraw", bountyHandler.NewBountyBudgetWithdraw) r.Get("/payment/status/{id}", bountyHandler.GetBountyPaymentStatus) r.Post("/", bountyHandler.CreateOrEditBounty) From f2d729b7267742a53818127d88a989d03943eb3b Mon Sep 17 00:00:00 2001 From: elraphty Date: Fri, 18 Oct 2024 17:03:40 +0100 Subject: [PATCH 21/24] changed db to default --- db/test_config.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/db/test_config.go b/db/test_config.go index 0e6131b27..4416b8b55 100644 --- a/db/test_config.go +++ b/db/test_config.go @@ -12,10 +12,10 @@ var TestDB database func InitTestDB() { rdsHost := "localhost" - rdsPort := fmt.Sprintf("%d", 5432) + rdsPort := fmt.Sprintf("%d", 5532) rdsDbName := "test_db" - rdsUsername := "raph" - rdsPassword := "raph" + rdsUsername := "test_user" + rdsPassword := "test_password" dbURL := fmt.Sprintf("postgres://%s:%s@%s:%s/%s", rdsUsername, rdsPassword, rdsHost, rdsPort, rdsDbName) if dbURL == "" { From d26873f9eabc6144159d339c6f3f713edb650dbf Mon Sep 17 00:00:00 2001 From: elraphty Date: Fri, 18 Oct 2024 17:22:49 +0100 Subject: [PATCH 22/24] removed invoice settlement check from handler --- db/workspaces.go | 2 -- handlers/workspaces.go | 13 ++----------- 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/db/workspaces.go b/db/workspaces.go index dade43e18..2d4549546 100644 --- a/db/workspaces.go +++ b/db/workspaces.go @@ -292,13 +292,11 @@ func (db database) ProcessUpdateBudget(invoice NewInvoiceList, getLightningInvoi if invoiceErr.Error != "" { tx.Rollback() - return errors.New("could not check invoice") } if !invoiceRes.Response.Settled { tx.Rollback() - return errors.New("invoice has not been settled") } diff --git a/handlers/workspaces.go b/handlers/workspaces.go index 6db71cf36..b132672f2 100644 --- a/handlers/workspaces.go +++ b/handlers/workspaces.go @@ -674,18 +674,9 @@ func (oh *workspaceHandler) PollBudgetInvoices(w http.ResponseWriter, r *http.Re workInvoices := oh.db.GetWorkspaceInvoices(uuid) for _, inv := range workInvoices { - invoiceRes, invoiceErr := oh.getLightningInvoice(inv.PaymentRequest) + if !inv.Status && inv.Type == "BUDGET" { + oh.db.ProcessUpdateBudget(inv, oh.getLightningInvoice) - if invoiceErr.Error != "" { - w.WriteHeader(http.StatusForbidden) - json.NewEncoder(w).Encode(invoiceErr) - return - } - - if invoiceRes.Response.Settled { - if !inv.Status && inv.Type == "BUDGET" { - oh.db.ProcessUpdateBudget(inv, oh.getLightningInvoice) - } } else { // Cheeck if time has expired isInvoiceExpired := utils.GetInvoiceExpired(inv.PaymentRequest) From 977073283a3344c3d0fa9c52c820b140a11b5508 Mon Sep 17 00:00:00 2001 From: elraphty Date: Fri, 18 Oct 2024 21:26:39 +0100 Subject: [PATCH 23/24] removed unused poll invoice code --- db/interface.go | 2 +- db/workspaces.go | 69 +++++++++++--------- handlers/bounty.go | 144 ++++++----------------------------------- handlers/workspaces.go | 15 ++++- mocks/Database.go | 21 +++--- 5 files changed, 84 insertions(+), 167 deletions(-) diff --git a/db/interface.go b/db/interface.go index d3d621e8a..d3473f6bd 100644 --- a/db/interface.go +++ b/db/interface.go @@ -106,7 +106,7 @@ type Database interface { GetWorkspaceBudget(workspace_uuid string) NewBountyBudget GetWorkspaceStatusBudget(workspace_uuid string) StatusBudget GetWorkspaceBudgetHistory(workspace_uuid string) []BudgetHistoryData - ProcessUpdateBudget(invoice NewInvoiceList, getLightningInvoice func(payment_request string) (InvoiceResult, InvoiceError)) error + ProcessUpdateBudget(invoice NewInvoiceList) error AddAndUpdateBudget(invoice NewInvoiceList) NewPaymentHistory WithdrawBudget(sender_pubkey string, workspace_uuid string, amount uint) AddPaymentHistory(payment NewPaymentHistory) NewPaymentHistory diff --git a/db/workspaces.go b/db/workspaces.go index 2d4549546..4b352a1a7 100644 --- a/db/workspaces.go +++ b/db/workspaces.go @@ -264,7 +264,7 @@ func (db database) GetWorkspaceBudgetHistory(workspace_uuid string) []BudgetHist return budgetHistory } -func (db database) ProcessUpdateBudget(invoice NewInvoiceList, getLightningInvoice func(payment_request string) (InvoiceResult, InvoiceError)) error { +func (db database) ProcessUpdateBudget(non_tx_invoice NewInvoiceList) error { // Start db transaction tx := db.db.Begin() @@ -280,32 +280,25 @@ func (db database) ProcessUpdateBudget(invoice NewInvoiceList, getLightningInvoi return err } - created := invoice.Created - workspace_uuid := invoice.WorkspaceUuid + created := non_tx_invoice.Created + workspace_uuid := non_tx_invoice.WorkspaceUuid + + invoice := NewInvoiceList{} + tx.Where("payment_request = ?", non_tx_invoice.PaymentRequest).Find(&invoice) if invoice.Status { tx.Rollback() return errors.New("cannot process already paid invoice") } - invoiceRes, invoiceErr := getLightningInvoice(invoice.PaymentRequest) - - if invoiceErr.Error != "" { - tx.Rollback() - return errors.New("could not check invoice") - } - - if !invoiceRes.Response.Settled { - tx.Rollback() - return errors.New("invoice has not been settled") - } - if workspace_uuid == "" { return errors.New("cannot Create a Workspace Without a Workspace uuid") } // Get payment history and update budget - paymentHistory := db.GetPaymentHistoryByCreated(created, workspace_uuid) + paymentHistory := NewPaymentHistory{} + tx.Model(&NewPaymentHistory{}).Where("created = ?", created).Where("workspace_uuid = ? ", workspace_uuid).Find(&paymentHistory) + if paymentHistory.WorkspaceUuid != "" && paymentHistory.Amount != 0 { paymentHistory.Status = true @@ -315,9 +308,10 @@ func (db database) ProcessUpdateBudget(invoice NewInvoiceList, getLightningInvoi } // get Workspace budget and add payment to total budget - WorkspaceBudget := db.GetWorkspaceBudget(workspace_uuid) + workspaceBudget := NewBountyBudget{} + tx.Model(&NewBountyBudget{}).Where("workspace_uuid = ?", workspace_uuid).Find(&workspaceBudget) - if WorkspaceBudget.WorkspaceUuid == "" { + if workspaceBudget.WorkspaceUuid == "" { now := time.Now() workBudget := NewBountyBudget{ WorkspaceUuid: workspace_uuid, @@ -330,11 +324,11 @@ func (db database) ProcessUpdateBudget(invoice NewInvoiceList, getLightningInvoi tx.Rollback() } } else { - totalBudget := WorkspaceBudget.TotalBudget - WorkspaceBudget.TotalBudget = totalBudget + paymentHistory.Amount + totalBudget := workspaceBudget.TotalBudget + workspaceBudget.TotalBudget = totalBudget + paymentHistory.Amount - if err = tx.Model(&NewBountyBudget{}).Where("workspace_uuid = ?", WorkspaceBudget.WorkspaceUuid).Updates(map[string]interface{}{ - "total_budget": WorkspaceBudget.TotalBudget, + if err = tx.Model(&NewBountyBudget{}).Where("workspace_uuid = ?", workspaceBudget.WorkspaceUuid).Updates(map[string]interface{}{ + "total_budget": workspaceBudget.TotalBudget, }).Error; err != nil { tx.Rollback() } @@ -350,19 +344,24 @@ func (db database) ProcessUpdateBudget(invoice NewInvoiceList, getLightningInvoi } func (db database) AddAndUpdateBudget(invoice NewInvoiceList) NewPaymentHistory { + // Start db transaction + tx := db.db.Begin() + created := invoice.Created workspace_uuid := invoice.WorkspaceUuid - paymentHistory := db.GetPaymentHistoryByCreated(created, workspace_uuid) + paymentHistory := NewPaymentHistory{} + tx.Model(&NewPaymentHistory{}).Where("created = ?", created).Where("workspace_uuid = ? ", workspace_uuid).Find(&paymentHistory) if paymentHistory.WorkspaceUuid != "" && paymentHistory.Amount != 0 { paymentHistory.Status = true db.db.Where("created = ?", created).Where("workspace_uuid = ? ", workspace_uuid).Updates(paymentHistory) // get Workspace budget and add payment to total budget - WorkspaceBudget := db.GetWorkspaceBudget(workspace_uuid) + workspaceBudget := NewBountyBudget{} + tx.Model(&NewBountyBudget{}).Where("workspace_uuid = ?", workspace_uuid).Find(&workspaceBudget) - if WorkspaceBudget.WorkspaceUuid == "" { + if workspaceBudget.WorkspaceUuid == "" { now := time.Now() workBudget := NewBountyBudget{ WorkspaceUuid: workspace_uuid, @@ -370,14 +369,26 @@ func (db database) AddAndUpdateBudget(invoice NewInvoiceList) NewPaymentHistory Created: &now, Updated: &now, } - db.CreateWorkspaceBudget(workBudget) + + if err := tx.Create(&workBudget).Error; err != nil { + tx.Rollback() + } } else { - totalBudget := WorkspaceBudget.TotalBudget - WorkspaceBudget.TotalBudget = totalBudget + paymentHistory.Amount - db.UpdateWorkspaceBudget(WorkspaceBudget) + totalBudget := workspaceBudget.TotalBudget + workspaceBudget.TotalBudget = totalBudget + paymentHistory.Amount + + if err := tx.Model(&NewBountyBudget{}).Where("workspace_uuid = ?", workspaceBudget.WorkspaceUuid).Updates(map[string]interface{}{ + "total_budget": workspaceBudget.TotalBudget, + }).Error; err != nil { + tx.Rollback() + } } + } else { + tx.Rollback() } + tx.Commit() + return paymentHistory } diff --git a/handlers/bounty.go b/handlers/bounty.go index 2c2077410..4216a61d5 100644 --- a/handlers/bounty.go +++ b/handlers/bounty.go @@ -907,8 +907,9 @@ func (h *bountyHandler) BountyBudgetWithdraw(w http.ResponseWriter, r *http.Requ if pubKeyFromAuth == "" { fmt.Println("[bounty] no pubkey from auth") - w.WriteHeader(http.StatusUnauthorized) h.m.Unlock() + + w.WriteHeader(http.StatusUnauthorized) return } @@ -917,15 +918,17 @@ func (h *bountyHandler) BountyBudgetWithdraw(w http.ResponseWriter, r *http.Requ r.Body.Close() if err != nil { - w.WriteHeader(http.StatusNotAcceptable) h.m.Unlock() + + w.WriteHeader(http.StatusNotAcceptable) return } err = json.Unmarshal(body, &request) if err != nil { - w.WriteHeader(http.StatusNotAcceptable) h.m.Unlock() + + w.WriteHeader(http.StatusNotAcceptable) return } @@ -940,11 +943,12 @@ func (h *bountyHandler) BountyBudgetWithdraw(w http.ResponseWriter, r *http.Requ // Check that last withdraw time is greater than 1 if hoursDiff < 1 { + h.m.Unlock() + w.WriteHeader(http.StatusUnauthorized) errMsg := formatPayError("Your last withdrawal is not more than an hour ago") log.Println("Your last withdrawal is not more than an hour ago", hoursDiff, lastWithdrawal.Created, request.WorkspaceUuid) json.NewEncoder(w).Encode(errMsg) - h.m.Unlock() return } } @@ -955,10 +959,11 @@ func (h *bountyHandler) BountyBudgetWithdraw(w http.ResponseWriter, r *http.Requ // or has a withdraw bounty budget role hasRole := h.userHasAccess(pubKeyFromAuth, request.WorkspaceUuid, db.WithdrawBudget) if !hasRole { + h.m.Unlock() + w.WriteHeader(http.StatusUnauthorized) errMsg := formatPayError("You don't have appropriate permissions to withdraw bounty budget") json.NewEncoder(w).Encode(errMsg) - h.m.Unlock() return } @@ -969,10 +974,11 @@ func (h *bountyHandler) BountyBudgetWithdraw(w http.ResponseWriter, r *http.Requ // is greater than the amount orgBudget := h.db.GetWorkspaceBudget(request.WorkspaceUuid) if amount > orgBudget.TotalBudget { + h.m.Unlock() + w.WriteHeader(http.StatusForbidden) errMsg := formatPayError("Workspace budget is not enough to withdraw the amount") json.NewEncoder(w).Encode(errMsg) - h.m.Unlock() return } @@ -981,10 +987,11 @@ func (h *bountyHandler) BountyBudgetWithdraw(w http.ResponseWriter, r *http.Requ sumOfDeposits := h.db.GetSumOfDeposits(request.WorkspaceUuid) if sumOfDeposits < sumOfWithdrawals+amount { + h.m.Unlock() + w.WriteHeader(http.StatusUnauthorized) errMsg := formatPayError("Your deposits is lesser than your withdral") json.NewEncoder(w).Encode(errMsg) - h.m.Unlock() return } @@ -992,19 +999,24 @@ func (h *bountyHandler) BountyBudgetWithdraw(w http.ResponseWriter, r *http.Requ if paymentSuccess.Success { // withdraw amount from workspace budget h.db.WithdrawBudget(pubKeyFromAuth, request.WorkspaceUuid, amount) + + h.m.Unlock() + w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(paymentSuccess) } else { + h.m.Unlock() + w.WriteHeader(http.StatusBadRequest) json.NewEncoder(w).Encode(paymentError) } } else { + h.m.Unlock() + w.WriteHeader(http.StatusForbidden) errMsg := formatPayError("Could not pay lightning invoice") json.NewEncoder(w).Encode(errMsg) } - - h.m.Unlock() } func formatPayError(errorMsg string) db.InvoicePayError { @@ -1281,17 +1293,13 @@ func (h *bountyHandler) GetInvoiceData(w http.ResponseWriter, r *http.Request) { } func (h *bountyHandler) PollInvoice(w http.ResponseWriter, r *http.Request) { - h.m.Lock() - ctx := r.Context() pubKeyFromAuth, _ := ctx.Value(auth.ContextKey).(string) paymentRequest := chi.URLParam(r, "paymentRequest") - var err error if pubKeyFromAuth == "" { fmt.Println("[bounty] no pubkey from auth") w.WriteHeader(http.StatusUnauthorized) - h.m.Unlock() return } @@ -1300,126 +1308,18 @@ func (h *bountyHandler) PollInvoice(w http.ResponseWriter, r *http.Request) { if invoiceErr.Error != "" { w.WriteHeader(http.StatusForbidden) json.NewEncoder(w).Encode(invoiceErr) - h.m.Unlock() return } if invoiceRes.Response.Settled { // Todo if an invoice is settled invoice := h.db.GetInvoice(paymentRequest) - invData := h.db.GetUserInvoiceData(paymentRequest) dbInvoice := h.db.GetInvoice(paymentRequest) // Make any change only if the invoice has not been settled if !dbInvoice.Status { - amount := invData.Amount if invoice.Type == "BUDGET" { h.db.AddAndUpdateBudget(invoice) - } else if invoice.Type == "KEYSEND" { - if config.IsV2Payment { - url := fmt.Sprintf("%s/pay", config.V2BotUrl) - - // Build v2 keysend payment data - bodyData := utils.BuildV2KeysendBodyData(amount, invData.UserPubkey, invData.RouteHint, "") - jsonBody := []byte(bodyData) - - req, _ := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(jsonBody)) - req.Header.Set("x-admin-token", config.V2BotToken) - req.Header.Set("Content-Type", "application/json") - log.Printf("[bounty] Making Bounty V2 Payment PollInvoice: amount: %d, pubkey: %s, route_hint: %s", amount, invData.UserPubkey, invData.RouteHint) - - res, err := h.httpClient.Do(req) - - if err != nil { - log.Printf("[bounty] Request Failed: %s", err) - h.m.Unlock() - return - } - - defer res.Body.Close() - body, err := io.ReadAll(res.Body) - if err != nil { - fmt.Println("[read body]", err) - w.WriteHeader(http.StatusNotAcceptable) - h.m.Unlock() - return - } - - // Unmarshal result - v2KeysendRes := db.V2SendOnionRes{} - err = json.Unmarshal(body, &v2KeysendRes) - - if err != nil { - fmt.Println("[Unmarshal]", err) - w.WriteHeader(http.StatusNotAcceptable) - h.m.Unlock() - return - } - - if res.StatusCode == 200 { - fmt.Println("V2 Status Code Is 200") - // if the payment has a completed status - if v2KeysendRes.Status == db.PaymentComplete { - fmt.Println("V2 Payment Is Completed") - bounty, err := h.db.GetBountyByCreated(uint(invData.Created)) - if err == nil { - now := time.Now() - bounty.Paid = true - bounty.PaidDate = &now - bounty.Completed = true - bounty.CompletionDate = &now - } - - h.db.UpdateBounty(bounty) - } - } else { - log.Printf("[bounty] V2 Keysend Payment to %s Failed, with Error: %s", invData.UserPubkey, err) - } - } else { - url := fmt.Sprintf("%s/payment", config.RelayUrl) - - bodyData := utils.BuildKeysendBodyData(amount, invData.UserPubkey, invData.RouteHint, "") - - jsonBody := []byte(bodyData) - - req, _ := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(jsonBody)) - - req.Header.Set("x-user-token", config.RelayAuthKey) - req.Header.Set("Content-Type", "application/json") - res, _ := h.httpClient.Do(req) - - defer res.Body.Close() - - body, _ := io.ReadAll(res.Body) - - if res.StatusCode == 200 { - // Unmarshal result - keysendRes := db.KeysendSuccess{} - err = json.Unmarshal(body, &keysendRes) - - if err != nil { - w.WriteHeader(http.StatusForbidden) - json.NewEncoder(w).Encode("Could not decode keysend response") - return - } - - bounty, err := h.db.GetBountyByCreated(uint(invData.Created)) - if err == nil { - now := time.Now() - bounty.Paid = true - bounty.PaidDate = &now - bounty.Completed = true - bounty.CompletionDate = &now - } - - h.db.UpdateBounty(bounty) - } else { - // Unmarshal result - keysendError := db.KeysendError{} - err = json.Unmarshal(body, &keysendError) - log.Printf("[bounty] Keysend Payment to %s Failed, with Error: %s", invData.UserPubkey, err) - } - } } // Update the invoice status h.db.UpdateInvoice(paymentRequest) @@ -1435,8 +1335,6 @@ func (h *bountyHandler) PollInvoice(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(invoiceRes) - - h.m.Unlock() } func GetFilterCount(w http.ResponseWriter, r *http.Request) { diff --git a/handlers/workspaces.go b/handlers/workspaces.go index b132672f2..d1f487f0c 100644 --- a/handlers/workspaces.go +++ b/handlers/workspaces.go @@ -674,9 +674,18 @@ func (oh *workspaceHandler) PollBudgetInvoices(w http.ResponseWriter, r *http.Re workInvoices := oh.db.GetWorkspaceInvoices(uuid) for _, inv := range workInvoices { - if !inv.Status && inv.Type == "BUDGET" { - oh.db.ProcessUpdateBudget(inv, oh.getLightningInvoice) + invoiceRes, invoiceErr := oh.getLightningInvoice(inv.PaymentRequest) + if invoiceErr.Error != "" { + w.WriteHeader(http.StatusForbidden) + json.NewEncoder(w).Encode(invoiceErr) + return + } + + if invoiceRes.Response.Settled { + if !inv.Status && inv.Type == "BUDGET" { + oh.db.ProcessUpdateBudget(inv) + } } else { // Cheeck if time has expired isInvoiceExpired := utils.GetInvoiceExpired(inv.PaymentRequest) @@ -720,7 +729,7 @@ func (oh *workspaceHandler) PollUserWorkspacesBudget(w http.ResponseWriter, r *h if invoiceRes.Response.Settled { if !inv.Status && inv.Type == "BUDGET" { - oh.db.ProcessUpdateBudget(inv, oh.getLightningInvoice) + oh.db.ProcessUpdateBudget(inv) } } else { // Cheeck if time has expired diff --git a/mocks/Database.go b/mocks/Database.go index 4e0bf60f8..4c40aeb6b 100644 --- a/mocks/Database.go +++ b/mocks/Database.go @@ -6823,17 +6823,17 @@ func (_c *Database_ProcessDeleteWorkspace_Call) RunAndReturn(run func(string) er return _c } -// ProcessUpdateBudget provides a mock function with given fields: invoice, getLightningInvoice -func (_m *Database) ProcessUpdateBudget(invoice db.NewInvoiceList, getLightningInvoice func(string) (db.InvoiceResult, db.InvoiceError)) error { - ret := _m.Called(invoice, getLightningInvoice) +// ProcessUpdateBudget provides a mock function with given fields: invoice +func (_m *Database) ProcessUpdateBudget(invoice db.NewInvoiceList) error { + ret := _m.Called(invoice) if len(ret) == 0 { panic("no return value specified for ProcessUpdateBudget") } var r0 error - if rf, ok := ret.Get(0).(func(db.NewInvoiceList, func(string) (db.InvoiceResult, db.InvoiceError)) error); ok { - r0 = rf(invoice, getLightningInvoice) + if rf, ok := ret.Get(0).(func(db.NewInvoiceList) error); ok { + r0 = rf(invoice) } else { r0 = ret.Error(0) } @@ -6848,14 +6848,13 @@ type Database_ProcessUpdateBudget_Call struct { // ProcessUpdateBudget is a helper method to define mock.On call // - invoice db.NewInvoiceList -// - getLightningInvoice func(string)(db.InvoiceResult , db.InvoiceError) -func (_e *Database_Expecter) ProcessUpdateBudget(invoice interface{}, getLightningInvoice interface{}) *Database_ProcessUpdateBudget_Call { - return &Database_ProcessUpdateBudget_Call{Call: _e.mock.On("ProcessUpdateBudget", invoice, getLightningInvoice)} +func (_e *Database_Expecter) ProcessUpdateBudget(invoice interface{}) *Database_ProcessUpdateBudget_Call { + return &Database_ProcessUpdateBudget_Call{Call: _e.mock.On("ProcessUpdateBudget", invoice)} } -func (_c *Database_ProcessUpdateBudget_Call) Run(run func(invoice db.NewInvoiceList, getLightningInvoice func(string) (db.InvoiceResult, db.InvoiceError))) *Database_ProcessUpdateBudget_Call { +func (_c *Database_ProcessUpdateBudget_Call) Run(run func(invoice db.NewInvoiceList)) *Database_ProcessUpdateBudget_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(db.NewInvoiceList), args[1].(func(string) (db.InvoiceResult, db.InvoiceError))) + run(args[0].(db.NewInvoiceList)) }) return _c } @@ -6865,7 +6864,7 @@ func (_c *Database_ProcessUpdateBudget_Call) Return(_a0 error) *Database_Process return _c } -func (_c *Database_ProcessUpdateBudget_Call) RunAndReturn(run func(db.NewInvoiceList, func(string) (db.InvoiceResult, db.InvoiceError)) error) *Database_ProcessUpdateBudget_Call { +func (_c *Database_ProcessUpdateBudget_Call) RunAndReturn(run func(db.NewInvoiceList) error) *Database_ProcessUpdateBudget_Call { _c.Call.Return(run) return _c } From 3d7aa60d64bcb40ae41e2bcd09db73e44b5274b0 Mon Sep 17 00:00:00 2001 From: elraphty Date: Fri, 18 Oct 2024 21:41:56 +0100 Subject: [PATCH 24/24] remove relay test for poll invoice --- handlers/bounty_test.go | 79 ----------------------------------------- 1 file changed, 79 deletions(-) diff --git a/handlers/bounty_test.go b/handlers/bounty_test.go index 2f36d8345..59f58802b 100644 --- a/handlers/bounty_test.go +++ b/handlers/bounty_test.go @@ -2140,85 +2140,6 @@ func TestPollInvoice(t *testing.T) { mockHttpClient.AssertExpectations(t) }) - t.Run("Should mock relay payment is successful update the bounty associated with the invoice and set the paid as true", func(t *testing.T) { - expectedUrl := fmt.Sprintf("%s/invoice?payment_request=%s", config.RelayUrl, invoice.PaymentRequest) - expectedBody := fmt.Sprintf(`{"success": true, "response": { "settled": true, "payment_request": "%s", "payment_hash": "payment_hash", "preimage": "preimage", "Amount": %d}}`, invoice.OwnerPubkey, bountyAmount) - - expectedV2Url := fmt.Sprintf("%s/check_invoice", botURL) - expectedV2InvoiceBody := `{"status": "paid", "amt_msat": "", "timestamp": ""}` - - r := io.NopCloser(bytes.NewReader([]byte(expectedBody))) - rv2 := io.NopCloser(bytes.NewReader([]byte(expectedV2InvoiceBody))) - - if botURL != "" && botToken != "" { - mockHttpClient.On("Do", mock.MatchedBy(func(req *http.Request) bool { - return req.Method == http.MethodPost && expectedV2Url == req.URL.String() && req.Header.Get("x-admin-token") == botToken - })).Return(&http.Response{ - StatusCode: 200, - Body: rv2, - }, nil).Once() - } else { - mockHttpClient.On("Do", mock.MatchedBy(func(req *http.Request) bool { - return req.Method == http.MethodGet && expectedUrl == req.URL.String() && req.Header.Get("x-user-token") == config.RelayAuthKey - })).Return(&http.Response{ - StatusCode: 200, - Body: r, - }, nil).Once() - } - - expectedPaymentUrl := fmt.Sprintf("%s/payment", config.RelayUrl) - expectedV2PaymentUrl := fmt.Sprintf("%s/pay", botURL) - - expectedPaymentBody := fmt.Sprintf(`{"amount": %d, "destination_key": "%s", "text": "memotext added for notification", "data": ""}`, bountyAmount, invoice.OwnerPubkey) - - expectedV2PaymentBody := - fmt.Sprintf(`{"amt_msat": %d, "dest": "%s", "route_hint": "%s", "data": "", "wait": true}`, bountyAmount*1000, invoice.OwnerPubkey, invoiceData.RouteHint) - - r2 := io.NopCloser(bytes.NewReader([]byte(`{"success": true, "response": { "sumAmount": "1"}}`))) - r3 := io.NopCloser(bytes.NewReader([]byte(`{"status": "COMPLETE", "amt_msat": "", "timestamp": "" }`))) - - if botURL != "" && botToken != "" { - mockHttpClient.On("Do", mock.MatchedBy(func(req *http.Request) bool { - bodyByt, _ := io.ReadAll(req.Body) - return req.Method == http.MethodPost && expectedV2PaymentUrl == req.URL.String() && req.Header.Get("x-admin-token") == botToken && expectedV2PaymentBody == string(bodyByt) - })).Return(&http.Response{ - StatusCode: 200, - Body: r3, - }, nil).Once() - } else { - mockHttpClient.On("Do", mock.MatchedBy(func(req *http.Request) bool { - bodyByt, _ := io.ReadAll(req.Body) - return req.Method == http.MethodPost && expectedPaymentUrl == req.URL.String() && req.Header.Get("x-user-token") == config.RelayAuthKey && expectedPaymentBody == string(bodyByt) - })).Return(&http.Response{ - StatusCode: 200, - Body: r2, - }, nil).Once() - } - - ro := chi.NewRouter() - ro.Post("/poll/invoice/{paymentRequest}", bHandler.PollInvoice) - - rr := httptest.NewRecorder() - req, err := http.NewRequestWithContext(authorizedCtx, http.MethodPost, "/poll/invoice/"+invoice.PaymentRequest, bytes.NewBufferString(`{}`)) - if err != nil { - t.Fatal(err) - } - - ro.ServeHTTP(rr, req) - - invData := db.TestDB.GetUserInvoiceData(invoice.PaymentRequest) - updatedBounty, err := db.TestDB.GetBountyByCreated(uint(invData.Created)) - if err != nil { - t.Fatal(err) - } - updatedInvoice := db.TestDB.GetInvoice(invoice.PaymentRequest) - - assert.True(t, updatedBounty.Paid, "Expected bounty to be marked as paid") - assert.True(t, updatedInvoice.Status, "Expected invoice status to be true") - assert.Equal(t, http.StatusOK, rr.Code) - mockHttpClient.AssertExpectations(t) - }) - t.Run("If the invoice is settled and the invoice.Type is equal to BUDGET the invoice amount should be added to the workspace budget and the payment status of the related invoice should be sent to true on the payment history table", func(t *testing.T) { db.TestDB.DeleteInvoice(paymentRequest)