fix: add err handling to db queries (#2064)

* fix: add err handling to db queries

* chore: add error handling to GetApp

* chore: wrap the original error

* fix: retrieve balance after err != nil check

* chore: minor code cleanup

* chore: add error handling to app permission listing in GetApp

* chore: convert GetBudgetUsageSat to GetBudgetUsage

* chore: add tests for GetBudgetUsage

* chore: separate budget window tests

---------

Co-authored-by: Roland Bewick <roland.bewick@gmail.com>
This commit is contained in:
Adithya Vardhan 2026-02-27 15:01:25 +05:30 committed by GitHub
parent 4a924c3d11
commit 3aa167a7c4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 387 additions and 45 deletions

View file

@ -406,12 +406,17 @@ func (api *api) DeleteLightningAddress(ctx context.Context, appId uint) error {
return nil
}
func (api *api) GetApp(dbApp *db.App) *App {
func (api *api) GetApp(dbApp *db.App) (*App, error) {
paySpecificPermission := db.AppPermission{}
appPermissions := []db.AppPermission{}
var expiresAt *time.Time
api.db.Where("app_id = ?", dbApp.ID).Find(&appPermissions)
if err := api.db.Where("app_id = ?", dbApp.ID).Find(&appPermissions).Error; err != nil {
logger.Logger.WithError(err).WithFields(logrus.Fields{
"app_id": dbApp.ID,
}).Error("Failed to list app permissions")
return nil, err
}
requestMethods := []string{}
for _, appPerm := range appPermissions {
@ -424,9 +429,14 @@ func (api *api) GetApp(dbApp *db.App) *App {
}
// renewsIn := ""
budgetUsage := uint64(0)
maxAmount := uint64(paySpecificPermission.MaxAmountSat)
budgetUsage = queries.GetBudgetUsageSat(api.db, &paySpecificPermission)
budgetUsage, err := queries.GetBudgetUsage(api.db, &paySpecificPermission)
if err != nil {
logger.Logger.WithError(err).WithFields(logrus.Fields{
"app_id": dbApp.ID,
}).Error("Failed to get budget usage for app")
return nil, err
}
var metadata Metadata
if dbApp.Metadata != nil {
@ -455,7 +465,7 @@ func (api *api) GetApp(dbApp *db.App) *App {
ExpiresAt: expiresAt,
MaxAmountSat: maxAmount,
Scopes: requestMethods,
BudgetUsage: budgetUsage,
BudgetUsage: budgetUsage / 1000,
BudgetRenewal: paySpecificPermission.BudgetRenewal,
Isolated: dbApp.Isolated,
Metadata: metadata,
@ -465,10 +475,17 @@ func (api *api) GetApp(dbApp *db.App) *App {
}
if dbApp.Isolated {
response.Balance = queries.GetIsolatedBalance(api.db, dbApp.ID)
balance, err := queries.GetIsolatedBalance(api.db, dbApp.ID)
if err != nil {
logger.Logger.WithError(err).WithFields(logrus.Fields{
"app_id": dbApp.ID,
}).Error("Failed to get isolated app balance")
return nil, err
}
response.Balance = balance
}
return &response
return &response, nil
}
func (api *api) ListApps(limit uint64, offset uint64, filters ListAppsFilters, orderBy string) (*ListAppsResponse, error) {
@ -586,7 +603,14 @@ func (api *api) ListApps(limit uint64, offset uint64, filters ListAppsFilters, o
}
if dbApp.Isolated {
apiApp.Balance = queries.GetIsolatedBalance(api.db, dbApp.ID)
balance, err := queries.GetIsolatedBalance(api.db, dbApp.ID)
if err != nil {
logger.Logger.WithError(err).WithFields(logrus.Fields{
"app_id": dbApp.ID,
}).Error("Failed to get isolated app balance")
return nil, err
}
apiApp.Balance = balance
}
for _, appPermission := range permissionsMap[dbApp.ID] {
@ -595,7 +619,14 @@ func (api *api) ListApps(limit uint64, offset uint64, filters ListAppsFilters, o
if appPermission.Scope == constants.PAY_INVOICE_SCOPE {
apiApp.BudgetRenewal = appPermission.BudgetRenewal
apiApp.MaxAmountSat = uint64(appPermission.MaxAmountSat)
apiApp.BudgetUsage = queries.GetBudgetUsageSat(api.db, &appPermission)
budgetUsage, err := queries.GetBudgetUsage(api.db, &appPermission)
if err != nil {
logger.Logger.WithError(err).WithFields(logrus.Fields{
"app_id": dbApp.ID,
}).Error("Failed to get budget usage for app")
return nil, err
}
apiApp.BudgetUsage = budgetUsage / 1000
}
}

View file

@ -16,7 +16,7 @@ type API interface {
UpdateApp(app *db.App, updateAppRequest *UpdateAppRequest) error
Transfer(ctx context.Context, fromAppId *uint, toAppId *uint, amountMsat uint64) error
DeleteApp(app *db.App) error
GetApp(app *db.App) *App
GetApp(app *db.App) (*App, error)
ListApps(limit uint64, offset uint64, filters ListAppsFilters, orderBy string) (*ListAppsResponse, error)
CreateLightningAddress(ctx context.Context, createLightningAddressRequest *CreateLightningAddressRequest) error
DeleteLightningAddress(ctx context.Context, appId uint) error

View file

@ -8,15 +8,18 @@ import (
"gorm.io/gorm"
)
func GetBudgetUsageSat(tx *gorm.DB, appPermission *db.AppPermission) uint64 {
func GetBudgetUsage(tx *gorm.DB, appPermission *db.AppPermission) (uint64, error) {
var result struct {
Sum uint64
}
tx.
err := tx.
Table("transactions").
Select("SUM(amount_msat + fee_msat + fee_reserve_msat) as sum").
Where("app_id = ? AND type = ? AND (state = ? OR state = ?) AND created_at > ?", appPermission.AppId, constants.TRANSACTION_TYPE_OUTGOING, constants.TRANSACTION_STATE_SETTLED, constants.TRANSACTION_STATE_PENDING, getStartOfBudget(appPermission.BudgetRenewal)).Scan(&result)
return result.Sum / 1000
Where("app_id = ? AND type = ? AND (state = ? OR state = ?) AND created_at > ?", appPermission.AppId, constants.TRANSACTION_TYPE_OUTGOING, constants.TRANSACTION_STATE_SETTLED, constants.TRANSACTION_STATE_PENDING, getStartOfBudget(appPermission.BudgetRenewal)).Scan(&result).Error
if err != nil {
return 0, err
}
return result.Sum, nil
}
func getStartOfBudget(budget_type string) time.Time {

View file

@ -0,0 +1,234 @@
package queries
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/getAlby/hub/constants"
"github.com/getAlby/hub/db"
"github.com/getAlby/hub/tests"
)
func TestGetBudgetUsage_IncludesPendingAndSettledOutgoing(t *testing.T) {
svc, err := tests.CreateTestService(t)
require.NoError(t, err)
defer svc.Remove()
app, _, err := tests.CreateApp(svc)
require.NoError(t, err)
appPermission := &db.AppPermission{
AppId: app.ID,
App: *app,
Scope: constants.PAY_INVOICE_SCOPE,
BudgetRenewal: constants.BUDGET_RENEWAL_NEVER,
}
require.NoError(t, svc.DB.Create(&db.Transaction{
AppId: &app.ID,
Type: constants.TRANSACTION_TYPE_OUTGOING,
State: constants.TRANSACTION_STATE_PENDING,
AmountMsat: 50000,
FeeMsat: 1000,
FeeReserveMsat: 2000,
}).Error)
require.NoError(t, svc.DB.Create(&db.Transaction{
AppId: &app.ID,
Type: constants.TRANSACTION_TYPE_OUTGOING,
State: constants.TRANSACTION_STATE_SETTLED,
AmountMsat: 25000,
FeeMsat: 500,
FeeReserveMsat: 500,
}).Error)
budgetUsage, err := GetBudgetUsage(svc.DB, appPermission)
require.NoError(t, err)
assert.Equal(t, uint64(79000), budgetUsage)
}
func TestGetBudgetUsage_ExcludesWrongStateTypeAndApp(t *testing.T) {
svc, err := tests.CreateTestService(t)
require.NoError(t, err)
defer svc.Remove()
app, _, err := tests.CreateApp(svc)
require.NoError(t, err)
otherApp, _, err := tests.CreateApp(svc)
require.NoError(t, err)
appPermission := &db.AppPermission{
AppId: app.ID,
App: *app,
Scope: constants.PAY_INVOICE_SCOPE,
BudgetRenewal: constants.BUDGET_RENEWAL_NEVER,
}
require.NoError(t, svc.DB.Create(&db.Transaction{
AppId: &app.ID,
Type: constants.TRANSACTION_TYPE_OUTGOING,
State: constants.TRANSACTION_STATE_SETTLED,
AmountMsat: 20000,
FeeMsat: 1000,
FeeReserveMsat: 0,
}).Error)
require.NoError(t, svc.DB.Create(&db.Transaction{
AppId: &app.ID,
Type: constants.TRANSACTION_TYPE_INCOMING,
State: constants.TRANSACTION_STATE_SETTLED,
AmountMsat: 90000,
FeeMsat: 0,
FeeReserveMsat: 0,
}).Error)
require.NoError(t, svc.DB.Create(&db.Transaction{
AppId: &app.ID,
Type: constants.TRANSACTION_TYPE_OUTGOING,
State: constants.TRANSACTION_STATE_FAILED,
AmountMsat: 90000,
FeeMsat: 0,
FeeReserveMsat: 0,
}).Error)
require.NoError(t, svc.DB.Create(&db.Transaction{
AppId: &otherApp.ID,
Type: constants.TRANSACTION_TYPE_OUTGOING,
State: constants.TRANSACTION_STATE_SETTLED,
AmountMsat: 90000,
FeeMsat: 0,
FeeReserveMsat: 0,
}).Error)
budgetUsage, err := GetBudgetUsage(svc.DB, appPermission)
require.NoError(t, err)
assert.Equal(t, uint64(21000), budgetUsage)
}
func TestGetBudgetUsage_BudgetWindowDaily(t *testing.T) {
svc, err := tests.CreateTestService(t)
require.NoError(t, err)
defer svc.Remove()
app, _, err := tests.CreateApp(svc)
require.NoError(t, err)
appPermissionDaily := &db.AppPermission{
AppId: app.ID,
App: *app,
Scope: constants.PAY_INVOICE_SCOPE,
BudgetRenewal: constants.BUDGET_RENEWAL_DAILY,
}
dailyStart := getStartOfBudget(constants.BUDGET_RENEWAL_DAILY)
require.NoError(t, svc.DB.Create(&db.Transaction{
AppId: &app.ID,
Type: constants.TRANSACTION_TYPE_OUTGOING,
State: constants.TRANSACTION_STATE_SETTLED,
AmountMsat: 20000,
FeeMsat: 1000,
FeeReserveMsat: 0,
CreatedAt: dailyStart.Add(1 * time.Minute),
}).Error)
require.NoError(t, svc.DB.Create(&db.Transaction{
AppId: &app.ID,
Type: constants.TRANSACTION_TYPE_OUTGOING,
State: constants.TRANSACTION_STATE_PENDING,
AmountMsat: 20000,
FeeMsat: 0,
FeeReserveMsat: 1000,
CreatedAt: dailyStart.Add(2 * time.Hour),
}).Error)
budgetUsage, err := GetBudgetUsage(svc.DB, appPermissionDaily)
require.NoError(t, err)
assert.Equal(t, uint64(42000), budgetUsage)
}
func TestGetBudgetUsage_BudgetWindowWeekly(t *testing.T) {
svc, err := tests.CreateTestService(t)
require.NoError(t, err)
defer svc.Remove()
app, _, err := tests.CreateApp(svc)
require.NoError(t, err)
appPermissionWeekly := &db.AppPermission{
AppId: app.ID,
App: *app,
Scope: constants.PAY_INVOICE_SCOPE,
BudgetRenewal: constants.BUDGET_RENEWAL_WEEKLY,
}
weeklyStart := getStartOfBudget(constants.BUDGET_RENEWAL_WEEKLY)
require.NoError(t, svc.DB.Create(&db.Transaction{
AppId: &app.ID,
Type: constants.TRANSACTION_TYPE_OUTGOING,
State: constants.TRANSACTION_STATE_SETTLED,
AmountMsat: 10000,
FeeMsat: 1000,
FeeReserveMsat: 0,
CreatedAt: weeklyStart.Add(30 * time.Minute),
}).Error)
require.NoError(t, svc.DB.Create(&db.Transaction{
AppId: &app.ID,
Type: constants.TRANSACTION_TYPE_OUTGOING,
State: constants.TRANSACTION_STATE_PENDING,
AmountMsat: 7000,
FeeMsat: 0,
FeeReserveMsat: 0,
CreatedAt: weeklyStart.Add(-1 * time.Hour),
}).Error)
budgetUsage, err := GetBudgetUsage(svc.DB, appPermissionWeekly)
require.NoError(t, err)
assert.Equal(t, uint64(11000), budgetUsage)
}
func TestGetBudgetUsage_BudgetWindowNever(t *testing.T) {
svc, err := tests.CreateTestService(t)
require.NoError(t, err)
defer svc.Remove()
app, _, err := tests.CreateApp(svc)
require.NoError(t, err)
appPermission := &db.AppPermission{
AppId: app.ID,
App: *app,
Scope: constants.PAY_INVOICE_SCOPE,
BudgetRenewal: constants.BUDGET_RENEWAL_NEVER,
}
require.NoError(t, svc.DB.Create(&db.Transaction{
AppId: &app.ID,
Type: constants.TRANSACTION_TYPE_OUTGOING,
State: constants.TRANSACTION_STATE_SETTLED,
AmountMsat: 5000,
FeeMsat: 1000,
FeeReserveMsat: 0,
CreatedAt: time.Now().AddDate(-2, 0, 0),
}).Error)
require.NoError(t, svc.DB.Create(&db.Transaction{
AppId: &app.ID,
Type: constants.TRANSACTION_TYPE_OUTGOING,
State: constants.TRANSACTION_STATE_PENDING,
AmountMsat: 4000,
FeeMsat: 0,
FeeReserveMsat: 1000,
CreatedAt: time.Now().AddDate(-1, 0, 0),
}).Error)
budgetUsage, err := GetBudgetUsage(svc.DB, appPermission)
require.NoError(t, err)
assert.Equal(t, uint64(11000), budgetUsage)
}

View file

@ -5,23 +5,29 @@ import (
"gorm.io/gorm"
)
func GetIsolatedBalance(tx *gorm.DB, appId uint) int64 {
func GetIsolatedBalance(tx *gorm.DB, appId uint) (int64, error) {
var received struct {
Sum int64
}
tx.
err := tx.
Table("transactions").
Select("SUM(amount_msat) as sum").
Where("app_id = ? AND type = ? AND state = ?", appId, constants.TRANSACTION_TYPE_INCOMING, constants.TRANSACTION_STATE_SETTLED).Scan(&received)
Where("app_id = ? AND type = ? AND state = ?", appId, constants.TRANSACTION_TYPE_INCOMING, constants.TRANSACTION_STATE_SETTLED).Scan(&received).Error
if err != nil {
return 0, err
}
var spent struct {
Sum int64
}
tx.
err = tx.
Table("transactions").
Select("SUM(amount_msat + fee_msat + fee_reserve_msat) as sum").
Where("app_id = ? AND type = ? AND (state = ? OR state = ?)", appId, constants.TRANSACTION_TYPE_OUTGOING, constants.TRANSACTION_STATE_SETTLED, constants.TRANSACTION_STATE_PENDING).Scan(&spent)
Where("app_id = ? AND type = ? AND (state = ? OR state = ?)", appId, constants.TRANSACTION_TYPE_OUTGOING, constants.TRANSACTION_STATE_SETTLED, constants.TRANSACTION_STATE_PENDING).Scan(&spent).Error
if err != nil {
return 0, err
}
return received.Sum - spent.Sum
return received.Sum - spent.Sum, nil
}

View file

@ -36,7 +36,8 @@ func TestGetIsolatedBalance_PendingNoOverflow(t *testing.T) {
}
svc.DB.Save(&tx)
balance := GetIsolatedBalance(svc.DB, app.ID)
balance, err := GetIsolatedBalance(svc.DB, app.ID)
require.NoError(t, err)
assert.Equal(t, int64(-11000), balance)
}
@ -65,6 +66,7 @@ func TestGetIsolatedBalance_SettledNoOverflow(t *testing.T) {
}
svc.DB.Save(&tx)
balance := GetIsolatedBalance(svc.DB, app.ID)
balance, err := GetIsolatedBalance(svc.DB, app.ID)
require.NoError(t, err)
assert.Equal(t, int64(-1000), balance)
}

View file

@ -1051,7 +1051,12 @@ func (httpSvc *HttpService) appsShowByPubkeyHandler(c echo.Context) error {
})
}
response := httpSvc.api.GetApp(dbApp)
response, err := httpSvc.api.GetApp(dbApp)
if err != nil {
return c.JSON(http.StatusInternalServerError, ErrorResponse{
Message: err.Error(),
})
}
return c.JSON(http.StatusOK, response)
}
@ -1079,7 +1084,12 @@ func (httpSvc *HttpService) appsShowHandler(c echo.Context) error {
})
}
response := httpSvc.api.GetApp(dbApp)
response, err := httpSvc.api.GetApp(dbApp)
if err != nil {
return c.JSON(http.StatusInternalServerError, ErrorResponse{
Message: err.Error(),
})
}
return c.JSON(http.StatusOK, response)
}

View file

@ -21,7 +21,6 @@ type getBalanceResponse struct {
// BudgetRenewal string `json:"budget_renewal"`
}
// TODO: remove checkPermission - can it be a middleware?
func (controller *nip47Controller) HandleGetBalanceEvent(ctx context.Context, nip47Request *models.Request, requestEventId uint, app *db.App, publishResponse publishFunc) {
logger.Logger.WithFields(logrus.Fields{
@ -30,10 +29,20 @@ func (controller *nip47Controller) HandleGetBalanceEvent(ctx context.Context, ni
balance := int64(0)
if app.Isolated {
balance = queries.GetIsolatedBalance(controller.db, app.ID)
var err error
balance, err = queries.GetIsolatedBalance(controller.db, app.ID)
if err != nil {
logger.Logger.WithFields(logrus.Fields{
"request_event_id": requestEventId,
}).WithError(err).Error("Failed to fetch isolated balance")
publishResponse(&models.Response{
ResultType: nip47Request.Method,
Error: mapNip47Error(err),
}, nostr.Tags{})
return
}
} else {
balances, err := controller.lnClient.GetBalances(ctx, true)
balance = balances.Lightning.TotalSpendable
if err != nil {
logger.Logger.WithFields(logrus.Fields{
"request_event_id": requestEventId,
@ -44,6 +53,7 @@ func (controller *nip47Controller) HandleGetBalanceEvent(ctx context.Context, ni
}, nostr.Tags{})
return
}
balance = balances.Lightning.TotalSpendable
}
responsePayload := &getBalanceResponse{

View file

@ -37,10 +37,21 @@ func (controller *nip47Controller) HandleGetBudgetEvent(ctx context.Context, nip
return
}
usedBudget := queries.GetBudgetUsageSat(controller.db, &appPermission)
usedBudget, err := queries.GetBudgetUsage(controller.db, &appPermission)
if err != nil {
logger.Logger.WithFields(logrus.Fields{
"request_event_id": requestEventId,
}).WithError(err).Error("Failed to fetch budget usage")
publishResponse(&models.Response{
ResultType: nip47Request.Method,
Error: mapNip47Error(err),
}, nostr.Tags{})
return
}
responsePayload := &getBudgetResponse{
TotalBudget: uint64(maxAmount * 1000),
UsedBudget: usedBudget * 1000,
UsedBudget: usedBudget,
RenewalPeriod: appPermission.BudgetRenewal,
RenewsAt: queries.GetBudgetRenewsAt(appPermission.BudgetRenewal),
}

View file

@ -399,7 +399,9 @@ func TestSendKeysend_IsolatedAppToNoApp(t *testing.T) {
result := svc.DB.Find(&transactions)
assert.Equal(t, int64(3), result.RowsAffected)
// expect balance to be decreased
assert.Equal(t, int64(10000), queries.GetIsolatedBalance(svc.DB, app.ID))
balance, err := queries.GetIsolatedBalance(svc.DB, app.ID)
assert.NoError(t, err)
assert.Equal(t, int64(10000), balance)
}
func TestSendKeysend_IsolatedAppToIsolatedApp(t *testing.T) {
@ -489,10 +491,14 @@ func TestSendKeysend_IsolatedAppToIsolatedApp(t *testing.T) {
result := svc.DB.Find(&transactions)
assert.Equal(t, int64(3), result.RowsAffected)
// expect balance to be decreased
assert.Equal(t, int64(10000), queries.GetIsolatedBalance(svc.DB, app.ID))
balance, err := queries.GetIsolatedBalance(svc.DB, app.ID)
assert.NoError(t, err)
assert.Equal(t, int64(10000), balance)
// expect app2 to receive the payment
assert.Equal(t, int64(123000), queries.GetIsolatedBalance(svc.DB, app2.ID))
balance, err = queries.GetIsolatedBalance(svc.DB, app2.ID)
assert.NoError(t, err)
assert.Equal(t, int64(123000), balance)
// check notifications
assert.Equal(t, 2, len(mockEventConsumer.GetConsumedEvents()))

View file

@ -107,7 +107,9 @@ func TestSendPaymentSync_SelfPayment_NoAppToIsolatedApp(t *testing.T) {
result := svc.DB.Find(&transactions)
assert.Equal(t, int64(2), result.RowsAffected)
// expect balance to be increased
assert.Equal(t, int64(123000), queries.GetIsolatedBalance(svc.DB, app.ID))
balance, err := queries.GetIsolatedBalance(svc.DB, app.ID)
assert.NoError(t, err)
assert.Equal(t, int64(123000), balance)
}
func TestSendPaymentSync_SelfPayment_NoAppToApp(t *testing.T) {
@ -230,7 +232,9 @@ func TestSendPaymentSync_SelfPayment_IsolatedAppToNoApp(t *testing.T) {
result := svc.DB.Find(&transactions)
assert.Equal(t, int64(3), result.RowsAffected)
// expect balance to be decreased
assert.Equal(t, int64(0), queries.GetIsolatedBalance(svc.DB, app.ID))
balance, err := queries.GetIsolatedBalance(svc.DB, app.ID)
assert.NoError(t, err)
assert.Equal(t, int64(0), balance)
}
func TestSendPaymentSync_SelfPayment_IsolatedAppToApp(t *testing.T) {
@ -306,7 +310,9 @@ func TestSendPaymentSync_SelfPayment_IsolatedAppToApp(t *testing.T) {
result := svc.DB.Find(&transactions)
assert.Equal(t, int64(3), result.RowsAffected)
// expect balance to be decreased
assert.Equal(t, int64(0), queries.GetIsolatedBalance(svc.DB, app.ID))
balance, err := queries.GetIsolatedBalance(svc.DB, app.ID)
assert.NoError(t, err)
assert.Equal(t, int64(0), balance)
}
func TestSendPaymentSync_SelfPayment_IsolatedAppToIsolatedApp(t *testing.T) {
@ -388,7 +394,9 @@ func TestSendPaymentSync_SelfPayment_IsolatedAppToIsolatedApp(t *testing.T) {
result := svc.DB.Find(&transactions)
assert.Equal(t, int64(3), result.RowsAffected)
// expect balance to be decreased
assert.Equal(t, int64(0), queries.GetIsolatedBalance(svc.DB, app.ID))
balance, err := queries.GetIsolatedBalance(svc.DB, app.ID)
assert.NoError(t, err)
assert.Equal(t, int64(0), balance)
// check notifications
assert.Equal(t, 2, len(mockEventConsumer.GetConsumedEvents()))
@ -481,7 +489,9 @@ func TestSendPaymentSync_SelfPayment_IsolatedAppToSelf(t *testing.T) {
assert.Equal(t, int64(3), result.RowsAffected)
// expect balance to be unchanged
assert.Equal(t, int64(123000), queries.GetIsolatedBalance(svc.DB, app.ID))
balance, err := queries.GetIsolatedBalance(svc.DB, app.ID)
assert.NoError(t, err)
assert.Equal(t, int64(123000), balance)
}
func TestSendPaymentSync_SelfPayment_IsolatedAppToApp_AmountProvidedIgnoredOnNonZeroAmountInvoice(t *testing.T) {
@ -560,5 +570,7 @@ func TestSendPaymentSync_SelfPayment_IsolatedAppToApp_AmountProvidedIgnoredOnNon
result := svc.DB.Find(&transactions)
assert.Equal(t, int64(3), result.RowsAffected)
// expect balance to be decreased
assert.Equal(t, int64(0), queries.GetIsolatedBalance(svc.DB, app.ID))
balance, err := queries.GetIsolatedBalance(svc.DB, app.ID)
assert.NoError(t, err)
assert.Equal(t, int64(0), balance)
}

View file

@ -1060,7 +1060,10 @@ func (svc *transactionsService) validateCanPay(tx *gorm.DB, appId *uint, amount
}
if app.Isolated {
balance := queries.GetIsolatedBalance(tx, appPermission.AppId)
balance, err := queries.GetIsolatedBalance(tx, appPermission.AppId)
if err != nil {
return fmt.Errorf("failed to calculate isolated balance for app: %w", err)
}
if int64(amountWithFeeReserve) > balance {
logger.Logger.WithFields(logrus.Fields{
@ -1087,8 +1090,11 @@ func (svc *transactionsService) validateCanPay(tx *gorm.DB, appId *uint, amount
}
if appPermission.MaxAmountSat > 0 {
budgetUsageSat := queries.GetBudgetUsageSat(tx, &appPermission)
if int(amountWithFeeReserve/1000) > appPermission.MaxAmountSat-int(budgetUsageSat) {
budgetUsage, err := queries.GetBudgetUsage(tx, &appPermission)
if err != nil {
return fmt.Errorf("failed to calculate budget usage for app: %w", err)
}
if int(amountWithFeeReserve/1000) > appPermission.MaxAmountSat-int(budgetUsage/1000) {
message := NewQuotaExceededError().Error()
if description != "" {
message += " " + description
@ -1429,9 +1435,14 @@ func (svc *transactionsService) checkBudgetUsage(dbTransaction *db.Transaction,
return
}
budgetUsage := queries.GetBudgetUsageSat(gormTransaction, &appPermission)
budgetUsage, err := queries.GetBudgetUsage(gormTransaction, &appPermission)
if err != nil {
logger.Logger.WithField("app_id", dbTransaction.AppId).WithError(err).Error("failed to get budget usage")
return
}
budgetUsageSat := budgetUsage / 1000
warningUsage := uint64(math.Floor(float64(appPermission.MaxAmountSat) * 0.8))
if budgetUsage >= warningUsage && budgetUsage-dbTransaction.AmountMsat/1000 < warningUsage {
if budgetUsageSat >= warningUsage && budgetUsageSat-dbTransaction.AmountMsat/1000 < warningUsage {
svc.eventPublisher.Publish(&events.Event{
Event: "nwc_budget_warning",
Properties: map[string]interface{}{

View file

@ -72,7 +72,10 @@ func (app *WailsApp) WailsRequestRouter(route string, method string, body string
switch method {
case "GET":
app := app.api.GetApp(dbApp)
app, err := app.api.GetApp(dbApp)
if err != nil {
return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
}
return WailsRequestRouterResponse{Body: app, Error: ""}
}
}
@ -93,7 +96,10 @@ func (app *WailsApp) WailsRequestRouter(route string, method string, body string
switch method {
case "GET":
app := app.api.GetApp(dbApp)
app, err := app.api.GetApp(dbApp)
if err != nil {
return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
}
return WailsRequestRouterResponse{Body: app, Error: ""}
case "PATCH":
updateAppRequest := &api.UpdateAppRequest{}