feat(treasurechest): #66 remove db interface for treasure chests
All checks were successful
Build Docker Image / Build-Docker-Image (push) Successful in 4m57s
Build and Push Docker Image / Build-And-Push-Docker-Image (push) Successful in 5m36s

This commit was merged in pull request #75.
This commit is contained in:
2025-05-16 12:30:01 +02:00
parent 7e244ccc07
commit c1a66bb261
3 changed files with 48 additions and 196 deletions

View File

@@ -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
}

View File

@@ -107,7 +107,6 @@ func createHandler(d *sqlx.DB, serverSettings *types.Settings) http.Handler {
var router = http.NewServeMux() var router = http.NewServeMux()
authDb := db.NewAuthSqlite(d) authDb := db.NewAuthSqlite(d)
treasureChestDb := db.NewTreasureChestSqlite(d)
randomService := service.NewRandom() randomService := service.NewRandom()
clockService := service.NewClock() 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) authService := service.NewAuth(authDb, randomService, clockService, mailService, serverSettings)
accountService := service.NewAccount(d, randomService, clockService, 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) transactionService := service.NewTransaction(d, randomService, clockService, serverSettings)
render := handler.NewRender() render := handler.NewRender()

View File

@@ -8,6 +8,7 @@ import (
"spend-sparrow/types" "spend-sparrow/types"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/jmoiron/sqlx"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto" "github.com/prometheus/client_golang/prometheus/promauto"
) )
@@ -31,13 +32,13 @@ type TreasureChest interface {
} }
type TreasureChestImpl struct { type TreasureChestImpl struct {
db db.TreasureChest db *sqlx.DB
clock Clock clock Clock
random Random random Random
settings *types.Settings 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{ return TreasureChestImpl{
db: db, db: db,
clock: clock, clock: clock,
@@ -90,17 +91,15 @@ func (s TreasureChestImpl) Add(user *types.User, parentId, name string) (*types.
UpdatedBy: nil, 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 { if err != nil {
return nil, types.ErrInternal return nil, err
} }
savedtreasureChest, err := s.db.Get(user.Id, newId) return treasureChest, nil
if err != nil {
log.Error("treasureChest %v not found after insert: %v", newId, err)
return nil, types.ErrInternal
}
return savedtreasureChest, nil
} }
func (s TreasureChestImpl) Update(user *types.User, idStr, parentId, name string) (*types.TreasureChest, error) { 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) 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 != nil {
if err == db.ErrNotFound { 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 return nil, types.ErrInternal
} }
@@ -132,16 +133,14 @@ func (s TreasureChestImpl) Update(user *types.User, idStr, parentId, name string
if err != nil { if err != nil {
return nil, err return nil, err
} }
if parent.ParentId != uuid.Nil { var childCount int
return nil, fmt.Errorf("only a depth of 1 allowed: %w", ErrBadRequest) 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)
children, err := s.db.GetAllByParentId(user.Id, treasureChest.Id)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if len(children) > 0 { if parent.ParentId != uuid.Nil || childCount > 0 {
return nil, fmt.Errorf("only a depth of 1 allowed: %w", ErrBadRequest) return nil, fmt.Errorf("only one level allowed: %w", ErrBadRequest)
} }
parentUuid = parent.Id parentUuid = parent.Id
@@ -153,9 +152,19 @@ 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
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 { if err != nil {
return nil, types.ErrInternal return nil, err
} }
return treasureChest, nil 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) 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 != nil {
if err == db.ErrNotFound { 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 return nil, types.ErrInternal
} }
@@ -190,9 +201,11 @@ func (s TreasureChestImpl) GetAll(user *types.User) ([]*types.TreasureChest, err
return nil, ErrUnauthorized 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 { if err != nil {
return nil, types.ErrInternal return nil, err
} }
return sortTree(treasureChests), nil 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) 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 != nil {
if err == db.ErrNotFound { return err
return fmt.Errorf("treasureChest %v not found: %w", idStr, ErrBadRequest)
}
return types.ErrInternal
} }
if treasureChest.UserId != user.Id { if childCount > 0 {
return types.ErrUnauthorized 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 { if err != nil {
log.Error("treasureChest delete: %v", err) return 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 nil return nil