From c1a66bb261ae14b973b3edd1e3a008b52b1ca25a Mon Sep 17 00:00:00 2001 From: Tim Wundenberg Date: Fri, 16 May 2025 12:30:01 +0200 Subject: [PATCH] feat(treasurechest): #66 remove db interface for treasure chests --- db/treasure_chest.go | 151 -------------------------------------- main.go | 3 +- service/treasure_chest.go | 90 ++++++++++++----------- 3 files changed, 48 insertions(+), 196 deletions(-) delete mode 100644 db/treasure_chest.go diff --git a/db/treasure_chest.go b/db/treasure_chest.go deleted file mode 100644 index f0e2dfe..0000000 --- a/db/treasure_chest.go +++ /dev/null @@ -1,151 +0,0 @@ -package db - -import ( - "database/sql" - "spend-sparrow/log" - "spend-sparrow/types" - - "github.com/google/uuid" - "github.com/jmoiron/sqlx" -) - -// While it may be duplicated to check for userId in the database access, it serves as a security layer -type TreasureChest interface { - Insert(userId uuid.UUID, treasureChest *types.TreasureChest) error - Update(userId uuid.UUID, treasureChest *types.TreasureChest) error - GetAll(userId uuid.UUID) ([]*types.TreasureChest, error) - GetAllByParentId(userId uuid.UUID, parentId uuid.UUID) ([]*types.TreasureChest, error) - Get(userId uuid.UUID, id uuid.UUID) (*types.TreasureChest, error) - Delete(userId uuid.UUID, id uuid.UUID) error -} - -type TreasureChestSqlite struct { - db *sqlx.DB -} - -func NewTreasureChestSqlite(db *sqlx.DB) *TreasureChestSqlite { - return &TreasureChestSqlite{db: db} -} - -func (db TreasureChestSqlite) Insert(userId uuid.UUID, treasureChest *types.TreasureChest) error { - - _, err := db.db.Exec(` - INSERT INTO treasure_chest (id, parent_id, user_id, name, current_balance, created_at, created_by) - VALUES (?,?,?,?,?,?,?)`, treasureChest.Id, treasureChest.ParentId, userId, treasureChest.Name, 0, treasureChest.CreatedAt, treasureChest.CreatedBy) - if err != nil { - log.Error("treasureChest Insert: %v", err) - return types.ErrInternal - } - - return nil -} - -func (db TreasureChestSqlite) Update(userId uuid.UUID, treasureChest *types.TreasureChest) error { - - r, err := db.db.Exec(` - UPDATE treasure_chest - SET - parent_id = ?, - name = ?, - current_balance = ?, - updated_at = ?, - updated_by = ? - WHERE id = ? - AND user_id = ?`, treasureChest.ParentId, treasureChest.Name, treasureChest.CurrentBalance, treasureChest.UpdatedAt, treasureChest.UpdatedBy, treasureChest.Id, userId) - if err != nil { - log.Error("treasureChest Update: %v", err) - return types.ErrInternal - } - rows, err := r.RowsAffected() - if err != nil { - log.Error("treasureChest Update: %v", err) - return types.ErrInternal - } - - if rows == 0 { - log.Info("treasureChest Update: not found") - return ErrNotFound - } - - return nil -} - -func (db TreasureChestSqlite) GetAll(userId uuid.UUID) ([]*types.TreasureChest, error) { - - treasureChests := make([]*types.TreasureChest, 0) - err := db.db.Select(&treasureChests, ` - SELECT - id, parent_id, user_id, name, current_balance, - created_at, created_by, updated_at, updated_by - FROM treasure_chest - WHERE user_id = ? - ORDER BY name`, userId) - if err != nil { - log.Error("treasureChest GetAll: %v", err) - return nil, types.ErrInternal - } - - return treasureChests, nil -} - -func (db TreasureChestSqlite) GetAllByParentId(userId uuid.UUID, parentId uuid.UUID) ([]*types.TreasureChest, error) { - - treasureChests := make([]*types.TreasureChest, 0) - err := db.db.Select(&treasureChests, ` - SELECT - id, parent_id, user_id, name, current_balance, - created_at, created_by, updated_at, updated_by - FROM treasure_chest - WHERE user_id = ? - AND parent_id = ? - ORDER BY name`, userId, parentId) - if err != nil { - log.Error("treasureChest GetAll: %v", err) - return nil, types.ErrInternal - } - - return treasureChests, nil -} - -func (db TreasureChestSqlite) Get(userId uuid.UUID, id uuid.UUID) (*types.TreasureChest, error) { - - treasureChest := &types.TreasureChest{} - err := db.db.Get(treasureChest, ` - SELECT - id, parent_id, user_id, name, current_balance, - created_at, created_by, updated_at, updated_by - FROM treasure_chest - WHERE user_id = ? - AND id = ?`, userId, id) - if err != nil { - if err == sql.ErrNoRows { - return nil, ErrNotFound - } - log.Error("treasureChest Get: %v", err) - return nil, types.ErrInternal - } - - return treasureChest, nil -} - -func (db TreasureChestSqlite) Delete(userId uuid.UUID, id uuid.UUID) error { - - res, err := db.db.Exec("DELETE FROM treasure_chest WHERE id = ? and user_id = ?", id, userId) - if err != nil { - log.Error("treasureChest Delete: %v", err) - return types.ErrInternal - } - - rows, err := res.RowsAffected() - if err != nil { - log.Error("treasureChest Delete: %v", err) - return types.ErrInternal - } - - if rows == 0 { - log.Info("treasureChest Delete: not found") - return ErrNotFound - } - - return nil -} diff --git a/main.go b/main.go index 5e37929..a501b17 100644 --- a/main.go +++ b/main.go @@ -107,7 +107,6 @@ func createHandler(d *sqlx.DB, serverSettings *types.Settings) http.Handler { var router = http.NewServeMux() authDb := db.NewAuthSqlite(d) - treasureChestDb := db.NewTreasureChestSqlite(d) randomService := service.NewRandom() clockService := service.NewClock() @@ -115,7 +114,7 @@ func createHandler(d *sqlx.DB, serverSettings *types.Settings) http.Handler { authService := service.NewAuth(authDb, randomService, clockService, mailService, serverSettings) accountService := service.NewAccount(d, randomService, clockService, serverSettings) - treasureChestService := service.NewTreasureChest(treasureChestDb, randomService, clockService, serverSettings) + treasureChestService := service.NewTreasureChest(d, randomService, clockService, serverSettings) transactionService := service.NewTransaction(d, randomService, clockService, serverSettings) render := handler.NewRender() diff --git a/service/treasure_chest.go b/service/treasure_chest.go index 316828b..ab02117 100644 --- a/service/treasure_chest.go +++ b/service/treasure_chest.go @@ -8,6 +8,7 @@ import ( "spend-sparrow/types" "github.com/google/uuid" + "github.com/jmoiron/sqlx" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" ) @@ -31,13 +32,13 @@ type TreasureChest interface { } type TreasureChestImpl struct { - db db.TreasureChest + db *sqlx.DB clock Clock random Random settings *types.Settings } -func NewTreasureChest(db db.TreasureChest, random Random, clock Clock, settings *types.Settings) TreasureChest { +func NewTreasureChest(db *sqlx.DB, random Random, clock Clock, settings *types.Settings) TreasureChest { return TreasureChestImpl{ db: db, clock: clock, @@ -90,17 +91,15 @@ func (s TreasureChestImpl) Add(user *types.User, parentId, name string) (*types. UpdatedBy: nil, } - err = s.db.Insert(user.Id, treasureChest) + r, err := s.db.NamedExec(` + INSERT INTO treasure_chest (id, parent_id, user_id, name, current_balance, created_at, created_by) + VALUES (:id, :parent_id, :user_id, :name, :current_balance, :created_at, :created_by)`, treasureChest) + err = db.TransformAndLogDbError("treasureChest Insert", r, err) if err != nil { - return nil, types.ErrInternal + return nil, err } - savedtreasureChest, err := s.db.Get(user.Id, newId) - if err != nil { - log.Error("treasureChest %v not found after insert: %v", newId, err) - return nil, types.ErrInternal - } - return savedtreasureChest, nil + return treasureChest, nil } func (s TreasureChestImpl) Update(user *types.User, idStr, parentId, name string) (*types.TreasureChest, error) { @@ -118,10 +117,12 @@ func (s TreasureChestImpl) Update(user *types.User, idStr, parentId, name string return nil, fmt.Errorf("could not parse Id: %w", ErrBadRequest) } - treasureChest, err := s.db.Get(user.Id, id) + treasureChest := &types.TreasureChest{} + err = s.db.Get(treasureChest, `SELECT * FROM treasure_chest WHERE user_id = ? AND id = ?`, user.Id, id) + err = db.TransformAndLogDbError("treasureChest Update", nil, err) if err != nil { if err == db.ErrNotFound { - return nil, fmt.Errorf("treasureChest %v not found: %w", idStr, ErrBadRequest) + return nil, fmt.Errorf("treasureChest %v not found: %w", idStr, err) } return nil, types.ErrInternal } @@ -132,16 +133,14 @@ func (s TreasureChestImpl) Update(user *types.User, idStr, parentId, name string if err != nil { return nil, err } - if parent.ParentId != uuid.Nil { - return nil, fmt.Errorf("only a depth of 1 allowed: %w", ErrBadRequest) - } - - children, err := s.db.GetAllByParentId(user.Id, treasureChest.Id) + var childCount int + err = s.db.Get(&childCount, `SELECT COUNT(*) FROM treasure_chest WHERE user_id = ? AND parent_id = ?`, user.Id, id) + err = db.TransformAndLogDbError("treasureChest Update", nil, err) if err != nil { return nil, err } - if len(children) > 0 { - return nil, fmt.Errorf("only a depth of 1 allowed: %w", ErrBadRequest) + if parent.ParentId != uuid.Nil || childCount > 0 { + return nil, fmt.Errorf("only one level allowed: %w", ErrBadRequest) } parentUuid = parent.Id @@ -153,9 +152,19 @@ func (s TreasureChestImpl) Update(user *types.User, idStr, parentId, name string treasureChest.UpdatedAt = ×tamp treasureChest.UpdatedBy = &user.Id - err = s.db.Update(user.Id, treasureChest) + r, err := s.db.NamedExec(` + UPDATE treasure_chest + SET + parent_id = :parent_id, + name = :name, + current_balance = :current_balance, + updated_at = :updated_at, + updated_by = :updated_by + WHERE id = :id + AND user_id = :user_id`, treasureChest) + err = db.TransformAndLogDbError("treasureChest Update", r, err) if err != nil { - return nil, types.ErrInternal + return nil, err } return treasureChest, nil @@ -173,10 +182,12 @@ func (s TreasureChestImpl) Get(user *types.User, id string) (*types.TreasureChes return nil, fmt.Errorf("could not parse Id: %w", ErrBadRequest) } - treasureChest, err := s.db.Get(user.Id, uuid) + treasureChest := &types.TreasureChest{} + err = s.db.Get(treasureChest, `SELECT * FROM treasure_chest WHERE user_id = ? AND id = ?`, user.Id, uuid) + err = db.TransformAndLogDbError("treasureChest Get", nil, err) if err != nil { if err == db.ErrNotFound { - return nil, fmt.Errorf("treasureChest %v not found: %w", id, ErrBadRequest) + return nil, fmt.Errorf("treasureChest %v not found: %w", id, err) } return nil, types.ErrInternal } @@ -190,9 +201,11 @@ func (s TreasureChestImpl) GetAll(user *types.User) ([]*types.TreasureChest, err return nil, ErrUnauthorized } - treasureChests, err := s.db.GetAll(user.Id) + treasureChests := make([]*types.TreasureChest, 0) + err := s.db.Select(&treasureChests, `SELECT * FROM treasure_chest WHERE user_id = ?`, user.Id) + err = db.TransformAndLogDbError("treasureChest GetAll", nil, err) if err != nil { - return nil, types.ErrInternal + return nil, err } return sortTree(treasureChests), nil @@ -209,30 +222,21 @@ func (s TreasureChestImpl) Delete(user *types.User, idStr string) error { return fmt.Errorf("could not parse Id: %w", ErrBadRequest) } - treasureChest, err := s.db.Get(user.Id, id) + childCount := 0 + err = s.db.Get(&childCount, `SELECT COUNT(*) FROM treasure_chest WHERE user_id = ? AND parent_id = ?`, user.Id, id) + err = db.TransformAndLogDbError("treasureChest Delete", nil, err) if err != nil { - if err == db.ErrNotFound { - return fmt.Errorf("treasureChest %v not found: %w", idStr, ErrBadRequest) - } - return types.ErrInternal + return err } - if treasureChest.UserId != user.Id { - return types.ErrUnauthorized + if childCount > 0 { + return fmt.Errorf("treasure chest has children: %w", ErrBadRequest) } - children, err := s.db.GetAllByParentId(user.Id, treasureChest.Id) + r, err := s.db.Exec(`DELETE FROM treasure_chest WHERE id = ? AND user_id = ?`, id, user.Id) + err = db.TransformAndLogDbError("treasureChest Delete", r, err) if err != nil { - log.Error("treasureChest delete: %v", err) - return types.ErrInternal - } - if len(children) > 0 { - return fmt.Errorf("TreasureChest %v has children: %w", idStr, ErrBadRequest) - } - - err = s.db.Delete(user.Id, treasureChest.Id) - if err != nil { - return types.ErrInternal + return err } return nil