feat: #74 properly use transactions
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 5m9s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 5m54s

This commit was merged in pull request #90.
This commit is contained in:
2025-05-16 23:28:49 +02:00
parent 6a254c09cf
commit 402a90f8f4
3 changed files with 135 additions and 29 deletions

View File

@@ -109,8 +109,17 @@ func (s AccountImpl) UpdateName(user *types.User, id string, name string) (*type
return nil, fmt.Errorf("could not parse Id: %w", ErrBadRequest) return nil, fmt.Errorf("could not parse Id: %w", ErrBadRequest)
} }
tx, err := s.db.Beginx()
err = db.TransformAndLogDbError("account Update", nil, err)
if err != nil {
return nil, err
}
defer func() {
_ = tx.Rollback()
}()
var account types.Account var account types.Account
err = s.db.Get(&account, `SELECT * FROM account WHERE user_id = ? AND id = ?`, user.Id, uuid) err = tx.Get(&account, `SELECT * FROM account WHERE user_id = ? AND id = ?`, user.Id, uuid)
err = db.TransformAndLogDbError("account Update", nil, err) err = db.TransformAndLogDbError("account Update", nil, err)
if err != nil { if err != nil {
if err == db.ErrNotFound { if err == db.ErrNotFound {
@@ -124,7 +133,7 @@ func (s AccountImpl) UpdateName(user *types.User, id string, name string) (*type
account.UpdatedAt = &timestamp account.UpdatedAt = &timestamp
account.UpdatedBy = &user.Id account.UpdatedBy = &user.Id
r, err := s.db.NamedExec(` r, err := tx.NamedExec(`
UPDATE account UPDATE account
SET SET
name = :name, name = :name,
@@ -137,6 +146,12 @@ func (s AccountImpl) UpdateName(user *types.User, id string, name string) (*type
return nil, err return nil, err
} }
err = tx.Commit()
err = db.TransformAndLogDbError("account Update", nil, err)
if err != nil {
return nil, err
}
return &account, nil return &account, nil
} }
@@ -192,8 +207,17 @@ func (s AccountImpl) Delete(user *types.User, id string) error {
return fmt.Errorf("could not parse Id: %w", ErrBadRequest) return fmt.Errorf("could not parse Id: %w", ErrBadRequest)
} }
tx, err := s.db.Beginx()
err = db.TransformAndLogDbError("account Delete", nil, err)
if err != nil {
return err
}
defer func() {
_ = tx.Rollback()
}()
transactionsCount := 0 transactionsCount := 0
err = s.db.Get(&transactionsCount, `SELECT COUNT(*) FROM "transaction" WHERE user_id = ? AND account_id = ?`, user.Id, uuid) err = tx.Get(&transactionsCount, `SELECT COUNT(*) FROM "transaction" WHERE user_id = ? AND account_id = ?`, user.Id, uuid)
err = db.TransformAndLogDbError("account Delete", nil, err) err = db.TransformAndLogDbError("account Delete", nil, err)
if err != nil { if err != nil {
return err return err
@@ -202,10 +226,17 @@ func (s AccountImpl) Delete(user *types.User, id string) error {
return fmt.Errorf("account has transactions, cannot delete: %w", ErrBadRequest) return fmt.Errorf("account has transactions, cannot delete: %w", ErrBadRequest)
} }
res, err := s.db.Exec("DELETE FROM account WHERE id = ? and user_id = ?", uuid, user.Id) res, err := tx.Exec("DELETE FROM account WHERE id = ? and user_id = ?", uuid, user.Id)
err = db.TransformAndLogDbError("account Delete", res, err) err = db.TransformAndLogDbError("account Delete", res, err)
if err != nil { if err != nil {
return err return err
} }
err = tx.Commit()
err = db.TransformAndLogDbError("account Delete", nil, err)
if err != nil {
return err
}
return nil return nil
} }

View File

@@ -58,12 +58,21 @@ func (s TransactionImpl) Add(user *types.User, transactionInput types.Transactio
return nil, ErrUnauthorized return nil, ErrUnauthorized
} }
transaction, err := s.validateAndEnrichTransaction(nil, user.Id, transactionInput) tx, err := s.db.Beginx()
err = db.TransformAndLogDbError("transaction Add", nil, err)
if err != nil {
return nil, err
}
defer func() {
_ = tx.Rollback()
}()
transaction, err := s.validateAndEnrichTransaction(tx, nil, user.Id, transactionInput)
if err != nil { if err != nil {
return nil, err return nil, err
} }
r, err := s.db.NamedExec(` r, err := tx.NamedExec(`
INSERT INTO "transaction" (id, user_id, account_id, treasure_chest_id, value, timestamp, note, error, created_at, created_by) INSERT INTO "transaction" (id, user_id, account_id, treasure_chest_id, value, timestamp, note, error, created_at, created_by)
VALUES (:id, :user_id, :account_id, :treasure_chest_id, :value, :timestamp, :note, :error, :created_at, :created_by)`, transaction) VALUES (:id, :user_id, :account_id, :treasure_chest_id, :value, :timestamp, :note, :error, :created_at, :created_by)`, transaction)
err = db.TransformAndLogDbError("transaction Insert", r, err) err = db.TransformAndLogDbError("transaction Insert", r, err)
@@ -72,27 +81,33 @@ func (s TransactionImpl) Add(user *types.User, transactionInput types.Transactio
} }
if transaction.AccountId != nil { if transaction.AccountId != nil {
r, err = s.db.Exec(` r, err = tx.Exec(`
UPDATE account UPDATE account
SET current_balance = current_balance + ? SET current_balance = current_balance + ?
WHERE id = ? AND user_id = ?`, transaction.Value, transaction.AccountId, user.Id) WHERE id = ? AND user_id = ?`, transaction.Value, transaction.AccountId, user.Id)
err = db.TransformAndLogDbError("transaction Update", r, err) err = db.TransformAndLogDbError("transaction Add", r, err)
if err != nil { if err != nil {
return nil, err return nil, err
} }
} }
if transaction.TreasureChestId != nil { if transaction.TreasureChestId != nil {
r, err = s.db.Exec(` r, err = tx.Exec(`
UPDATE treasure_chest UPDATE treasure_chest
SET current_balance = current_balance + ? SET current_balance = current_balance + ?
WHERE id = ? AND user_id = ?`, transaction.Value, transaction.TreasureChestId, user.Id) WHERE id = ? AND user_id = ?`, transaction.Value, transaction.TreasureChestId, user.Id)
err = db.TransformAndLogDbError("transaction Update", r, err) err = db.TransformAndLogDbError("transaction Add", r, err)
if err != nil { if err != nil {
return nil, err return nil, err
} }
} }
err = tx.Commit()
err = db.TransformAndLogDbError("transaction Add", nil, err)
if err != nil {
return nil, err
}
return transaction, nil return transaction, nil
} }
@@ -107,8 +122,17 @@ func (s TransactionImpl) Update(user *types.User, input types.TransactionInput)
return nil, fmt.Errorf("could not parse Id: %w", ErrBadRequest) return nil, fmt.Errorf("could not parse Id: %w", ErrBadRequest)
} }
tx, err := s.db.Beginx()
err = db.TransformAndLogDbError("transaction Update", nil, err)
if err != nil {
return nil, err
}
defer func() {
_ = tx.Rollback()
}()
transaction := &types.Transaction{} transaction := &types.Transaction{}
err = s.db.Get(transaction, `SELECT * FROM "transaction" WHERE user_id = ? AND id = ?`, user.Id, uuid) err = tx.Get(transaction, `SELECT * FROM "transaction" WHERE user_id = ? AND id = ?`, user.Id, uuid)
err = db.TransformAndLogDbError("transaction Update", nil, err) err = db.TransformAndLogDbError("transaction Update", nil, err)
if err != nil { if err != nil {
if err == db.ErrNotFound { if err == db.ErrNotFound {
@@ -118,7 +142,7 @@ func (s TransactionImpl) Update(user *types.User, input types.TransactionInput)
} }
if transaction.AccountId != nil { if transaction.AccountId != nil {
r, err := s.db.Exec(` r, err := tx.Exec(`
UPDATE account UPDATE account
SET current_balance = current_balance - ? SET current_balance = current_balance - ?
WHERE id = ? AND user_id = ?`, transaction.Value, transaction.AccountId, user.Id) WHERE id = ? AND user_id = ?`, transaction.Value, transaction.AccountId, user.Id)
@@ -128,7 +152,7 @@ func (s TransactionImpl) Update(user *types.User, input types.TransactionInput)
} }
} }
if transaction.TreasureChestId != nil { if transaction.TreasureChestId != nil {
r, err := s.db.Exec(` r, err := tx.Exec(`
UPDATE treasure_chest UPDATE treasure_chest
SET current_balance = current_balance - ? SET current_balance = current_balance - ?
WHERE id = ? AND user_id = ?`, transaction.Value, transaction.TreasureChestId, user.Id) WHERE id = ? AND user_id = ?`, transaction.Value, transaction.TreasureChestId, user.Id)
@@ -138,13 +162,13 @@ func (s TransactionImpl) Update(user *types.User, input types.TransactionInput)
} }
} }
transaction, err = s.validateAndEnrichTransaction(transaction, user.Id, input) transaction, err = s.validateAndEnrichTransaction(tx, transaction, user.Id, input)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if transaction.AccountId != nil { if transaction.AccountId != nil {
r, err := s.db.Exec(` r, err := tx.Exec(`
UPDATE account UPDATE account
SET current_balance = current_balance + ? SET current_balance = current_balance + ?
WHERE id = ? AND user_id = ?`, transaction.Value, transaction.AccountId, user.Id) WHERE id = ? AND user_id = ?`, transaction.Value, transaction.AccountId, user.Id)
@@ -154,7 +178,7 @@ func (s TransactionImpl) Update(user *types.User, input types.TransactionInput)
} }
} }
if transaction.TreasureChestId != nil { if transaction.TreasureChestId != nil {
r, err := s.db.Exec(` r, err := tx.Exec(`
UPDATE treasure_chest UPDATE treasure_chest
SET current_balance = current_balance + ? SET current_balance = current_balance + ?
WHERE id = ? AND user_id = ?`, transaction.Value, transaction.TreasureChestId, user.Id) WHERE id = ? AND user_id = ?`, transaction.Value, transaction.TreasureChestId, user.Id)
@@ -164,7 +188,7 @@ func (s TransactionImpl) Update(user *types.User, input types.TransactionInput)
} }
} }
r, err := s.db.NamedExec(` r, err := tx.NamedExec(`
UPDATE "transaction" UPDATE "transaction"
SET SET
account_id = :account_id, account_id = :account_id,
@@ -182,6 +206,12 @@ func (s TransactionImpl) Update(user *types.User, input types.TransactionInput)
return nil, err return nil, err
} }
err = tx.Commit()
err = db.TransformAndLogDbError("transaction Update", nil, err)
if err != nil {
return nil, err
}
return transaction, nil return transaction, nil
} }
@@ -236,7 +266,16 @@ func (s TransactionImpl) Delete(user *types.User, id string) error {
return fmt.Errorf("could not parse Id: %w", ErrBadRequest) return fmt.Errorf("could not parse Id: %w", ErrBadRequest)
} }
r, err := s.db.Exec(` tx, err := s.db.Beginx()
err = db.TransformAndLogDbError("transaction Delete", nil, err)
if err != nil {
return nil
}
defer func() {
_ = tx.Rollback()
}()
r, err := tx.Exec(`
UPDATE account UPDATE account
SET current_balance = current_balance - (SELECT value FROM "transaction" WHERE id = ? AND user_id = ?) SET current_balance = current_balance - (SELECT value FROM "transaction" WHERE id = ? AND user_id = ?)
WHERE id = (SELECT account_id FROM "transaction" WHERE id = ? AND user_id = ?) WHERE id = (SELECT account_id FROM "transaction" WHERE id = ? AND user_id = ?)
@@ -245,7 +284,7 @@ func (s TransactionImpl) Delete(user *types.User, id string) error {
if err != nil && err != db.ErrNotFound { if err != nil && err != db.ErrNotFound {
return err return err
} }
r, err = s.db.Exec(` r, err = tx.Exec(`
UPDATE treasure_chest UPDATE treasure_chest
SET current_balance = current_balance - (SELECT value FROM "transaction" WHERE id = ? AND user_id = ?) SET current_balance = current_balance - (SELECT value FROM "transaction" WHERE id = ? AND user_id = ?)
WHERE id = (SELECT treasure_chest_id FROM "transaction" WHERE id = ? AND user_id = ?) WHERE id = (SELECT treasure_chest_id FROM "transaction" WHERE id = ? AND user_id = ?)
@@ -255,12 +294,18 @@ func (s TransactionImpl) Delete(user *types.User, id string) error {
return err return err
} }
r, err = s.db.Exec("DELETE FROM \"transaction\" WHERE id = ? AND user_id = ?", uuid, user.Id) r, err = tx.Exec("DELETE FROM \"transaction\" WHERE id = ? AND user_id = ?", uuid, user.Id)
err = db.TransformAndLogDbError("transaction Delete", r, err) err = db.TransformAndLogDbError("transaction Delete", r, err)
if err != nil { if err != nil {
return err return err
} }
err = tx.Commit()
err = db.TransformAndLogDbError("transaction RecalculateBalances", nil, err)
if err != nil {
return err
}
return nil return nil
} }
@@ -365,7 +410,7 @@ func (s TransactionImpl) RecalculateBalances(user *types.User) error {
return nil return nil
} }
func (s TransactionImpl) validateAndEnrichTransaction(oldTransaction *types.Transaction, userId uuid.UUID, input types.TransactionInput) (*types.Transaction, error) { func (s TransactionImpl) validateAndEnrichTransaction(tx *sqlx.Tx, oldTransaction *types.Transaction, userId uuid.UUID, input types.TransactionInput) (*types.Transaction, error) {
var ( var (
id uuid.UUID id uuid.UUID
@@ -403,7 +448,7 @@ func (s TransactionImpl) validateAndEnrichTransaction(oldTransaction *types.Tran
return nil, fmt.Errorf("could not parse accountId: %w", ErrBadRequest) return nil, fmt.Errorf("could not parse accountId: %w", ErrBadRequest)
} }
accountUuid = &temp accountUuid = &temp
err = s.db.Get(&rowCount, `SELECT COUNT(*) FROM account WHERE id = ? AND user_id = ?`, accountUuid, userId) err = tx.Get(&rowCount, `SELECT COUNT(*) FROM account WHERE id = ? AND user_id = ?`, accountUuid, userId)
err = db.TransformAndLogDbError("transaction validate", nil, err) err = db.TransformAndLogDbError("transaction validate", nil, err)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -422,7 +467,7 @@ func (s TransactionImpl) validateAndEnrichTransaction(oldTransaction *types.Tran
return nil, fmt.Errorf("could not parse treasureChestId: %w", ErrBadRequest) return nil, fmt.Errorf("could not parse treasureChestId: %w", ErrBadRequest)
} }
treasureChestUuid = &temp treasureChestUuid = &temp
err = s.db.Get(&rowCount, `SELECT COUNT(*) FROM treasure_chest WHERE id = ? AND user_id = ?`, treasureChestUuid, userId) err = tx.Get(&rowCount, `SELECT COUNT(*) FROM treasure_chest WHERE id = ? AND user_id = ?`, treasureChestUuid, userId)
err = db.TransformAndLogDbError("transaction validate", nil, err) err = db.TransformAndLogDbError("transaction validate", nil, err)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@@ -117,8 +117,17 @@ func (s TreasureChestImpl) Update(user *types.User, idStr, parentId, name string
return nil, fmt.Errorf("could not parse Id: %w", ErrBadRequest) return nil, fmt.Errorf("could not parse Id: %w", ErrBadRequest)
} }
tx, err := s.db.Beginx()
err = db.TransformAndLogDbError("treasureChest Update", nil, err)
if err != nil {
return nil, err
}
defer func() {
_ = tx.Rollback()
}()
treasureChest := &types.TreasureChest{} treasureChest := &types.TreasureChest{}
err = s.db.Get(treasureChest, `SELECT * FROM treasure_chest WHERE user_id = ? AND id = ?`, user.Id, id) err = tx.Get(treasureChest, `SELECT * FROM treasure_chest WHERE user_id = ? AND id = ?`, user.Id, id)
err = db.TransformAndLogDbError("treasureChest Update", nil, err) err = db.TransformAndLogDbError("treasureChest Update", nil, err)
if err != nil { if err != nil {
if err == db.ErrNotFound { if err == db.ErrNotFound {
@@ -134,7 +143,7 @@ func (s TreasureChestImpl) Update(user *types.User, idStr, parentId, name string
return nil, err return nil, err
} }
var childCount int var childCount int
err = s.db.Get(&childCount, `SELECT COUNT(*) FROM treasure_chest WHERE user_id = ? AND parent_id = ?`, user.Id, id) err = tx.Get(&childCount, `SELECT COUNT(*) FROM treasure_chest WHERE user_id = ? AND parent_id = ?`, user.Id, id)
err = db.TransformAndLogDbError("treasureChest Update", nil, err) err = db.TransformAndLogDbError("treasureChest Update", nil, err)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -152,7 +161,7 @@ func (s TreasureChestImpl) Update(user *types.User, idStr, parentId, name string
treasureChest.UpdatedAt = &timestamp treasureChest.UpdatedAt = &timestamp
treasureChest.UpdatedBy = &user.Id treasureChest.UpdatedBy = &user.Id
r, err := s.db.NamedExec(` r, err := tx.NamedExec(`
UPDATE treasure_chest UPDATE treasure_chest
SET SET
parent_id = :parent_id, parent_id = :parent_id,
@@ -167,6 +176,12 @@ func (s TreasureChestImpl) Update(user *types.User, idStr, parentId, name string
return nil, err return nil, err
} }
err = tx.Commit()
err = db.TransformAndLogDbError("treasureChest Update", nil, err)
if err != nil {
return nil, err
}
return treasureChest, nil return treasureChest, nil
} }
@@ -222,8 +237,17 @@ func (s TreasureChestImpl) Delete(user *types.User, idStr string) error {
return fmt.Errorf("could not parse Id: %w", ErrBadRequest) return fmt.Errorf("could not parse Id: %w", ErrBadRequest)
} }
tx, err := s.db.Beginx()
err = db.TransformAndLogDbError("treasureChest Delete", nil, err)
if err != nil {
return nil
}
defer func() {
_ = tx.Rollback()
}()
childCount := 0 childCount := 0
err = s.db.Get(&childCount, `SELECT COUNT(*) FROM treasure_chest WHERE user_id = ? AND parent_id = ?`, user.Id, id) err = tx.Get(&childCount, `SELECT COUNT(*) FROM treasure_chest WHERE user_id = ? AND parent_id = ?`, user.Id, id)
err = db.TransformAndLogDbError("treasureChest Delete", nil, err) err = db.TransformAndLogDbError("treasureChest Delete", nil, err)
if err != nil { if err != nil {
return err return err
@@ -234,7 +258,7 @@ func (s TreasureChestImpl) Delete(user *types.User, idStr string) error {
} }
transactionsCount := 0 transactionsCount := 0
err = s.db.Get(&transactionsCount, `SELECT COUNT(*) FROM "transaction" WHERE user_id = ? AND treasure_chest_id = ?`, user.Id, id) err = tx.Get(&transactionsCount, `SELECT COUNT(*) FROM "transaction" WHERE user_id = ? AND treasure_chest_id = ?`, user.Id, id)
err = db.TransformAndLogDbError("treasureChest Delete", nil, err) err = db.TransformAndLogDbError("treasureChest Delete", nil, err)
if err != nil { if err != nil {
return err return err
@@ -243,12 +267,18 @@ func (s TreasureChestImpl) Delete(user *types.User, idStr string) error {
return fmt.Errorf("treasure chest has transactions: %w", ErrBadRequest) return fmt.Errorf("treasure chest has transactions: %w", ErrBadRequest)
} }
r, err := s.db.Exec(`DELETE FROM treasure_chest WHERE id = ? AND user_id = ?`, id, user.Id) r, err := tx.Exec(`DELETE FROM treasure_chest WHERE id = ? AND user_id = ?`, id, user.Id)
err = db.TransformAndLogDbError("treasureChest Delete", r, err) err = db.TransformAndLogDbError("treasureChest Delete", r, err)
if err != nil { if err != nil {
return err return err
} }
err = tx.Commit()
err = db.TransformAndLogDbError("treasureChest Delete", nil, err)
if err != nil {
return err
}
return nil return nil
} }