feat: implement account service and db
This commit is contained in:
+124
-96
@@ -1,130 +1,158 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"spend-sparrow/db"
|
||||
"spend-sparrow/types"
|
||||
|
||||
"errors"
|
||||
"strconv"
|
||||
"time"
|
||||
"regexp"
|
||||
|
||||
"spend-sparrow/db"
|
||||
"spend-sparrow/log"
|
||||
"spend-sparrow/types"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type Workout interface {
|
||||
AddWorkout(user *types.User, workoutDto *WorkoutDto) (*WorkoutDto, error)
|
||||
DeleteWorkout(user *types.User, rowId int) error
|
||||
GetWorkouts(user *types.User) ([]*WorkoutDto, error)
|
||||
var (
|
||||
safeInputRegex = regexp.MustCompile(`^[a-zA-Z0-9-]+$`)
|
||||
)
|
||||
|
||||
type Account interface {
|
||||
Add(user *types.User, name string) (*types.Account, error)
|
||||
Update(user *types.User, id uuid.UUID, name string) (*types.Account, error)
|
||||
Get(user *types.User) ([]*types.Account, error)
|
||||
Delete(user *types.User, id uuid.UUID) error
|
||||
}
|
||||
|
||||
type WorkoutImpl struct {
|
||||
db db.WorkoutDb
|
||||
random Random
|
||||
type AccountImpl struct {
|
||||
db db.Account
|
||||
clock Clock
|
||||
mail Mail
|
||||
random Random
|
||||
settings *types.Settings
|
||||
}
|
||||
|
||||
func NewWorkoutImpl(db db.WorkoutDb, random Random, clock Clock, mail Mail, settings *types.Settings) Workout {
|
||||
return WorkoutImpl{
|
||||
func NewAccountImpl(db db.Account, clock Clock, random Random, settings *types.Settings) Account {
|
||||
return AccountImpl{
|
||||
db: db,
|
||||
random: random,
|
||||
clock: clock,
|
||||
mail: mail,
|
||||
random: NewRandomImpl(),
|
||||
settings: settings,
|
||||
}
|
||||
}
|
||||
|
||||
type AccountDto struct {
|
||||
}
|
||||
|
||||
type TransactionDto struct {
|
||||
}
|
||||
|
||||
func NewWorkoutDtoFromDb(workout *db.Workout) *WorkoutDto {
|
||||
return &WorkoutDto{
|
||||
RowId: strconv.Itoa(workout.RowId),
|
||||
Date: renderDate(workout.Date),
|
||||
Type: workout.Type,
|
||||
Sets: strconv.Itoa(workout.Sets),
|
||||
Reps: strconv.Itoa(workout.Reps),
|
||||
}
|
||||
}
|
||||
func NewWorkoutDto(rowId string, date string, workoutType string, sets string, reps string) *WorkoutDto {
|
||||
return &WorkoutDto{
|
||||
RowId: rowId,
|
||||
Date: date,
|
||||
Type: workoutType,
|
||||
Sets: sets,
|
||||
Reps: reps,
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
ErrInputValues = errors.New("invalid input values")
|
||||
)
|
||||
|
||||
func (service WorkoutImpl) AddWorkout(user *types.User, workoutDto *WorkoutDto) (*WorkoutDto, error) {
|
||||
|
||||
if workoutDto.Date == "" || workoutDto.Type == "" || workoutDto.Sets == "" || workoutDto.Reps == "" {
|
||||
return nil, ErrInputValues
|
||||
}
|
||||
|
||||
date, err := time.Parse("2006-01-02", workoutDto.Date)
|
||||
if err != nil {
|
||||
return nil, ErrInputValues
|
||||
}
|
||||
|
||||
sets, err := strconv.Atoi(workoutDto.Sets)
|
||||
if err != nil {
|
||||
return nil, ErrInputValues
|
||||
}
|
||||
|
||||
reps, err := strconv.Atoi(workoutDto.Reps)
|
||||
if err != nil {
|
||||
return nil, ErrInputValues
|
||||
}
|
||||
|
||||
workoutInsert := db.NewWorkoutInsert(date, workoutDto.Type, sets, reps)
|
||||
|
||||
workout, err := service.db.InsertWorkout(user.Id, workoutInsert)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewWorkoutDtoFromDb(workout), nil
|
||||
}
|
||||
|
||||
func (service WorkoutImpl) DeleteWorkout(user *types.User, rowId int) error {
|
||||
if user == nil {
|
||||
return types.ErrInternal
|
||||
}
|
||||
|
||||
return service.db.DeleteWorkout(user.Id, rowId)
|
||||
}
|
||||
|
||||
func (service WorkoutImpl) GetWorkouts(user *types.User) ([]*WorkoutDto, error) {
|
||||
func (service AccountImpl) Add(user *types.User, name string) (*types.Account, error) {
|
||||
if user == nil {
|
||||
return nil, types.ErrInternal
|
||||
}
|
||||
|
||||
workouts, err := service.db.GetWorkouts(user.Id)
|
||||
newId, err := service.random.UUID()
|
||||
if err != nil {
|
||||
return nil, types.ErrInternal
|
||||
}
|
||||
|
||||
err = service.validateAccount(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// for _, workout := range workouts {
|
||||
// workout.Date = renderDate(workout.Date)
|
||||
// }
|
||||
account := &types.Account{
|
||||
Id: newId,
|
||||
GroupId: user.Id,
|
||||
|
||||
workoutsDto := make([]*WorkoutDto, len(workouts))
|
||||
for i, workout := range workouts {
|
||||
workoutsDto[i] = NewWorkoutDtoFromDb(&workout)
|
||||
Name: name,
|
||||
|
||||
CurrentBalance: 0,
|
||||
LastTransaction: nil,
|
||||
OinkBalance: 0,
|
||||
|
||||
CreatedAt: service.clock.Now(),
|
||||
CreatedBy: user.Id,
|
||||
UpdatedAt: nil,
|
||||
UpdatedBy: nil,
|
||||
}
|
||||
|
||||
return workoutsDto, nil
|
||||
err = service.db.Insert(account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
savedAccount, err := service.db.Get(user.Id, newId)
|
||||
if err != nil {
|
||||
if errors.Is(err, db.ErrNotFound) {
|
||||
log.Error("Account not found after insert: %v", err)
|
||||
}
|
||||
return nil, types.ErrInternal
|
||||
}
|
||||
return savedAccount, nil
|
||||
}
|
||||
|
||||
func renderDate(date time.Time) string {
|
||||
return date.Format("2006-01-02")
|
||||
func (service AccountImpl) Update(user *types.User, id uuid.UUID, name string) (*types.Account, error) {
|
||||
if user == nil {
|
||||
return nil, types.ErrInternal
|
||||
}
|
||||
err := service.validateAccount(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
account, err := service.db.Get(user.Id, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
timestamp := service.clock.Now()
|
||||
account.Name = name
|
||||
account.UpdatedAt = ×tamp
|
||||
account.UpdatedBy = &user.Id
|
||||
|
||||
err = service.db.Update(account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return account, nil
|
||||
}
|
||||
|
||||
func (service AccountImpl) Get(user *types.User) ([]*types.Account, error) {
|
||||
|
||||
if user == nil {
|
||||
return nil, types.ErrInternal
|
||||
}
|
||||
|
||||
accounts, err := service.db.GetAll(user.GroupId)
|
||||
if err != nil {
|
||||
return nil, types.ErrInternal
|
||||
}
|
||||
|
||||
return accounts, nil
|
||||
}
|
||||
|
||||
func (service AccountImpl) Delete(user *types.User, id uuid.UUID) error {
|
||||
if user == nil {
|
||||
return types.ErrInternal
|
||||
}
|
||||
|
||||
account, err := service.db.Get(user.GroupId, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if account.GroupId != user.GroupId {
|
||||
return types.ErrUnauthorized
|
||||
}
|
||||
|
||||
err = service.db.Delete(account.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (service AccountImpl) validateAccount(name string) error {
|
||||
if name == "" {
|
||||
return errors.New("Please enter a value for the \"name\" field.")
|
||||
} else if !safeInputRegex.MatchString(name) {
|
||||
return errors.New("Please use only letters, dashes or numbers for \"name\".")
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,130 +0,0 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"spend-sparrow/db"
|
||||
"spend-sparrow/types"
|
||||
|
||||
"errors"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Workout interface {
|
||||
AddWorkout(user *types.User, workoutDto *WorkoutDto) (*WorkoutDto, error)
|
||||
DeleteWorkout(user *types.User, rowId int) error
|
||||
GetWorkouts(user *types.User) ([]*WorkoutDto, error)
|
||||
}
|
||||
|
||||
type WorkoutImpl struct {
|
||||
db db.WorkoutDb
|
||||
random Random
|
||||
clock Clock
|
||||
mail Mail
|
||||
settings *types.Settings
|
||||
}
|
||||
|
||||
func NewWorkoutImpl(db db.WorkoutDb, random Random, clock Clock, mail Mail, settings *types.Settings) Workout {
|
||||
return WorkoutImpl{
|
||||
db: db,
|
||||
random: random,
|
||||
clock: clock,
|
||||
mail: mail,
|
||||
settings: settings,
|
||||
}
|
||||
}
|
||||
|
||||
type WorkoutDto struct {
|
||||
RowId string
|
||||
Date string
|
||||
Type string
|
||||
Sets string
|
||||
Reps string
|
||||
}
|
||||
|
||||
func NewWorkoutDtoFromDb(workout *db.Workout) *WorkoutDto {
|
||||
return &WorkoutDto{
|
||||
RowId: strconv.Itoa(workout.RowId),
|
||||
Date: renderDate(workout.Date),
|
||||
Type: workout.Type,
|
||||
Sets: strconv.Itoa(workout.Sets),
|
||||
Reps: strconv.Itoa(workout.Reps),
|
||||
}
|
||||
}
|
||||
func NewWorkoutDto(rowId string, date string, workoutType string, sets string, reps string) *WorkoutDto {
|
||||
return &WorkoutDto{
|
||||
RowId: rowId,
|
||||
Date: date,
|
||||
Type: workoutType,
|
||||
Sets: sets,
|
||||
Reps: reps,
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
ErrInputValues = errors.New("invalid input values")
|
||||
)
|
||||
|
||||
func (service WorkoutImpl) AddWorkout(user *types.User, workoutDto *WorkoutDto) (*WorkoutDto, error) {
|
||||
|
||||
if workoutDto.Date == "" || workoutDto.Type == "" || workoutDto.Sets == "" || workoutDto.Reps == "" {
|
||||
return nil, ErrInputValues
|
||||
}
|
||||
|
||||
date, err := time.Parse("2006-01-02", workoutDto.Date)
|
||||
if err != nil {
|
||||
return nil, ErrInputValues
|
||||
}
|
||||
|
||||
sets, err := strconv.Atoi(workoutDto.Sets)
|
||||
if err != nil {
|
||||
return nil, ErrInputValues
|
||||
}
|
||||
|
||||
reps, err := strconv.Atoi(workoutDto.Reps)
|
||||
if err != nil {
|
||||
return nil, ErrInputValues
|
||||
}
|
||||
|
||||
workoutInsert := db.NewWorkoutInsert(date, workoutDto.Type, sets, reps)
|
||||
|
||||
workout, err := service.db.InsertWorkout(user.Id, workoutInsert)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewWorkoutDtoFromDb(workout), nil
|
||||
}
|
||||
|
||||
func (service WorkoutImpl) DeleteWorkout(user *types.User, rowId int) error {
|
||||
if user == nil {
|
||||
return types.ErrInternal
|
||||
}
|
||||
|
||||
return service.db.DeleteWorkout(user.Id, rowId)
|
||||
}
|
||||
|
||||
func (service WorkoutImpl) GetWorkouts(user *types.User) ([]*WorkoutDto, error) {
|
||||
if user == nil {
|
||||
return nil, types.ErrInternal
|
||||
}
|
||||
|
||||
workouts, err := service.db.GetWorkouts(user.Id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// for _, workout := range workouts {
|
||||
// workout.Date = renderDate(workout.Date)
|
||||
// }
|
||||
|
||||
workoutsDto := make([]*WorkoutDto, len(workouts))
|
||||
for i, workout := range workouts {
|
||||
workoutsDto[i] = NewWorkoutDtoFromDb(&workout)
|
||||
}
|
||||
|
||||
return workoutsDto, nil
|
||||
}
|
||||
|
||||
func renderDate(date time.Time) string {
|
||||
return date.Format("2006-01-02")
|
||||
}
|
||||
+69
-101
@@ -2,111 +2,79 @@ package service
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"spend-sparrow/types"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestMoneyCalculation(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("should calculate correct oink balance", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
underTest := NewMoneyImpl()
|
||||
|
||||
// GIVEN
|
||||
timestamp := time.Date(2020, 01, 01, 0, 0, 0, 0, time.UTC)
|
||||
|
||||
groupId := uuid.New()
|
||||
|
||||
account := types.Account{
|
||||
Id: uuid.New(),
|
||||
GroupId: groupId,
|
||||
|
||||
Type: "Bank",
|
||||
Name: "Bank",
|
||||
|
||||
CurrentBalance: 0,
|
||||
LastTransaction: time.Time{},
|
||||
OinkBalance: 0,
|
||||
}
|
||||
|
||||
// The PiggyBank is a fictional account. The money it "holds" is actually in the Account
|
||||
piggyBank := types.PiggyBank{
|
||||
Id: uuid.New(),
|
||||
GroupId: groupId,
|
||||
|
||||
AccountId: account.Id,
|
||||
Name: "Car",
|
||||
|
||||
CurrentBalance: 0,
|
||||
}
|
||||
|
||||
savingsPlan := types.SavingsPlan{
|
||||
Id: uuid.New(),
|
||||
GroupId: groupId,
|
||||
PiggyBankId: piggyBank.Id,
|
||||
|
||||
MonthlySaving: 10,
|
||||
|
||||
ValidFrom: timestamp,
|
||||
}
|
||||
|
||||
transaction1 := types.Transaction{
|
||||
Id: uuid.New(),
|
||||
GroupId: groupId,
|
||||
|
||||
AccountId: account.Id,
|
||||
|
||||
Amount: 20,
|
||||
Timestamp: timestamp,
|
||||
}
|
||||
|
||||
transaction2 := types.Transaction{
|
||||
Id: uuid.New(),
|
||||
GroupId: groupId,
|
||||
|
||||
AccountId: account.Id,
|
||||
PiggyBankId: &piggyBank.Id,
|
||||
|
||||
Amount: -1,
|
||||
Timestamp: timestamp.Add(1 * time.Hour),
|
||||
}
|
||||
|
||||
expected := []types.BalanceInTime{
|
||||
{
|
||||
Id: uuid.New(),
|
||||
GroupId: groupId,
|
||||
|
||||
TranactionId: transaction1.Id,
|
||||
AccountId: account.Id,
|
||||
|
||||
ValidFrom: timestamp,
|
||||
Balance: 20,
|
||||
OinkBalance: 10,
|
||||
},
|
||||
{
|
||||
Id: uuid.New(),
|
||||
GroupId: groupId,
|
||||
|
||||
TranactionId: transaction2.Id,
|
||||
AccountId: account.Id,
|
||||
PiggyBankId: piggyBank.Id,
|
||||
|
||||
ValidFrom: timestamp.Add(1 * time.Hour),
|
||||
Balance: 19,
|
||||
OinkBalance: 9,
|
||||
},
|
||||
}
|
||||
|
||||
// WHEN
|
||||
actual, err := underTest.CalculateAllBalancesInTime(account, piggyBank, savingsPlan, []types.Transaction{transaction1, transaction2})
|
||||
|
||||
// THEN
|
||||
assert.Nil(t, err)
|
||||
assert.ElementsMatch(t, expected, actual)
|
||||
// t.Parallel()
|
||||
//
|
||||
// underTest := NewMoneyImpl()
|
||||
//
|
||||
// // GIVEN
|
||||
// timestamp := time.Date(2020, 01, 01, 0, 0, 0, 0, time.UTC)
|
||||
//
|
||||
// groupId := uuid.New()
|
||||
//
|
||||
// account := types.Account{
|
||||
// Id: uuid.New(),
|
||||
// GroupId: groupId,
|
||||
//
|
||||
// Type: "Bank",
|
||||
// Name: "Bank",
|
||||
//
|
||||
// CurrentBalance: 0,
|
||||
// LastTransaction: time.Time{},
|
||||
// OinkBalance: 0,
|
||||
// }
|
||||
//
|
||||
// // The PiggyBank is a fictional account. The money it "holds" is actually in the Account
|
||||
// piggyBank := types.PiggyBank{
|
||||
// Id: uuid.New(),
|
||||
// GroupId: groupId,
|
||||
//
|
||||
// AccountId: account.Id,
|
||||
// Name: "Car",
|
||||
//
|
||||
// CurrentBalance: 0,
|
||||
// }
|
||||
//
|
||||
// savingsPlan := types.SavingsPlan{
|
||||
// Id: uuid.New(),
|
||||
// GroupId: groupId,
|
||||
// PiggyBankId: piggyBank.Id,
|
||||
//
|
||||
// MonthlySaving: 10,
|
||||
//
|
||||
// ValidFrom: timestamp,
|
||||
// }
|
||||
//
|
||||
// transaction1 := types.Transaction{
|
||||
// Id: uuid.New(),
|
||||
// GroupId: groupId,
|
||||
//
|
||||
// AccountId: account.Id,
|
||||
//
|
||||
// Value: 20,
|
||||
// Timestamp: timestamp,
|
||||
// }
|
||||
//
|
||||
// transaction2 := types.Transaction{
|
||||
// Id: uuid.New(),
|
||||
// GroupId: groupId,
|
||||
//
|
||||
// AccountId: account.Id,
|
||||
// PiggyBankId: &piggyBank.Id,
|
||||
//
|
||||
// Value: -1,
|
||||
// Timestamp: timestamp.Add(1 * time.Hour),
|
||||
// }
|
||||
//
|
||||
// // WHEN
|
||||
// actual, err := underTest.CalculateAllBalancesInTime(account, piggyBank, savingsPlan, []types.Transaction{transaction1, transaction2})
|
||||
//
|
||||
// // THEN
|
||||
// assert.Nil(t, err)
|
||||
// assert.ElementsMatch(t, expected, actual)
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user