219 lines
5.0 KiB
Go
219 lines
5.0 KiB
Go
package service
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"spend-sparrow/internal/db"
|
|
"spend-sparrow/internal/log"
|
|
"spend-sparrow/internal/types"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/jmoiron/sqlx"
|
|
)
|
|
|
|
type Account interface {
|
|
Add(user *types.User, name string) (*types.Account, error)
|
|
UpdateName(user *types.User, id string, name string) (*types.Account, error)
|
|
Get(user *types.User, id string) (*types.Account, error)
|
|
GetAll(user *types.User) ([]*types.Account, error)
|
|
Delete(user *types.User, id string) error
|
|
}
|
|
|
|
type AccountImpl struct {
|
|
db *sqlx.DB
|
|
clock Clock
|
|
random Random
|
|
}
|
|
|
|
func NewAccount(db *sqlx.DB, random Random, clock Clock) Account {
|
|
return AccountImpl{
|
|
db: db,
|
|
clock: clock,
|
|
random: random,
|
|
}
|
|
}
|
|
|
|
func (s AccountImpl) Add(user *types.User, name string) (*types.Account, error) {
|
|
if user == nil {
|
|
return nil, ErrUnauthorized
|
|
}
|
|
|
|
newId, err := s.random.UUID()
|
|
if err != nil {
|
|
return nil, types.ErrInternal
|
|
}
|
|
|
|
err = validateString(name, "name")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
account := &types.Account{
|
|
Id: newId,
|
|
UserId: user.Id,
|
|
|
|
Name: name,
|
|
|
|
CurrentBalance: 0,
|
|
LastTransaction: nil,
|
|
OinkBalance: 0,
|
|
|
|
CreatedAt: s.clock.Now(),
|
|
CreatedBy: user.Id,
|
|
UpdatedAt: nil,
|
|
UpdatedBy: nil,
|
|
}
|
|
|
|
r, err := s.db.NamedExec(`
|
|
INSERT INTO account (id, user_id, name, current_balance, oink_balance, created_at, created_by)
|
|
VALUES (:id, :user_id, :name, :current_balance, :oink_balance, :created_at, :created_by)`, account)
|
|
err = db.TransformAndLogDbError("account Insert", r, err)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return account, nil
|
|
}
|
|
|
|
func (s AccountImpl) UpdateName(user *types.User, id string, name string) (*types.Account, error) {
|
|
if user == nil {
|
|
return nil, ErrUnauthorized
|
|
}
|
|
err := validateString(name, "name")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
uuid, err := uuid.Parse(id)
|
|
if err != nil {
|
|
log.L.Error("account update", "err", err)
|
|
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
|
|
err = tx.Get(&account, `SELECT * FROM account WHERE user_id = ? AND id = ?`, user.Id, uuid)
|
|
err = db.TransformAndLogDbError("account Update", nil, err)
|
|
if err != nil {
|
|
if errors.Is(err, db.ErrNotFound) {
|
|
return nil, fmt.Errorf("account %v not found: %w", id, ErrBadRequest)
|
|
}
|
|
return nil, types.ErrInternal
|
|
}
|
|
|
|
timestamp := s.clock.Now()
|
|
account.Name = name
|
|
account.UpdatedAt = ×tamp
|
|
account.UpdatedBy = &user.Id
|
|
|
|
r, err := tx.NamedExec(`
|
|
UPDATE account
|
|
SET
|
|
name = :name,
|
|
updated_at = :updated_at,
|
|
updated_by = :updated_by
|
|
WHERE id = :id
|
|
AND user_id = :user_id`, account)
|
|
err = db.TransformAndLogDbError("account Update", r, err)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = tx.Commit()
|
|
err = db.TransformAndLogDbError("account Update", nil, err)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &account, nil
|
|
}
|
|
|
|
func (s AccountImpl) Get(user *types.User, id string) (*types.Account, error) {
|
|
if user == nil {
|
|
return nil, ErrUnauthorized
|
|
}
|
|
uuid, err := uuid.Parse(id)
|
|
if err != nil {
|
|
log.L.Error("account get", "err", err)
|
|
return nil, fmt.Errorf("could not parse Id: %w", ErrBadRequest)
|
|
}
|
|
|
|
var account types.Account
|
|
err = s.db.Get(&account, `
|
|
SELECT * FROM account WHERE user_id = ? AND id = ?`, user.Id, uuid)
|
|
err = db.TransformAndLogDbError("account Get", nil, err)
|
|
if err != nil {
|
|
log.L.Error("account get", "err", err)
|
|
return nil, err
|
|
}
|
|
|
|
return &account, nil
|
|
}
|
|
|
|
func (s AccountImpl) GetAll(user *types.User) ([]*types.Account, error) {
|
|
if user == nil {
|
|
return nil, ErrUnauthorized
|
|
}
|
|
|
|
accounts := make([]*types.Account, 0)
|
|
err := s.db.Select(&accounts, `
|
|
SELECT * FROM account WHERE user_id = ? ORDER BY name`, user.Id)
|
|
err = db.TransformAndLogDbError("account GetAll", nil, err)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return accounts, nil
|
|
}
|
|
|
|
func (s AccountImpl) Delete(user *types.User, id string) error {
|
|
if user == nil {
|
|
return ErrUnauthorized
|
|
}
|
|
uuid, err := uuid.Parse(id)
|
|
if err != nil {
|
|
log.L.Error("account delete", "err", err)
|
|
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
|
|
err = tx.Get(&transactionsCount, `SELECT COUNT(*) FROM "transaction" WHERE user_id = ? AND account_id = ?`, user.Id, uuid)
|
|
err = db.TransformAndLogDbError("account Delete", nil, err)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if transactionsCount > 0 {
|
|
return fmt.Errorf("account has transactions, cannot delete: %w", ErrBadRequest)
|
|
}
|
|
|
|
res, err := tx.Exec("DELETE FROM account WHERE id = ? and user_id = ?", uuid, user.Id)
|
|
err = db.TransformAndLogDbError("account Delete", res, err)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = tx.Commit()
|
|
err = db.TransformAndLogDbError("account Delete", nil, err)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|