From 0792d8e01a46721255196bfb2650d44b8c93eac7 Mon Sep 17 00:00:00 2001 From: Tim Wundenberg Date: Sat, 10 May 2025 23:22:09 +0200 Subject: [PATCH 1/2] feat: extract types to seperate files --- types/account.go | 27 +++++++++++++ types/money.go | 91 ------------------------------------------- types/savings_plan.go | 26 +++++++++++++ types/transaction.go | 37 ++++++++++++++++++ types/treasure.go | 24 ++++++++++++ 5 files changed, 114 insertions(+), 91 deletions(-) create mode 100644 types/account.go delete mode 100644 types/money.go create mode 100644 types/savings_plan.go create mode 100644 types/transaction.go create mode 100644 types/treasure.go diff --git a/types/account.go b/types/account.go new file mode 100644 index 0000000..f0376ec --- /dev/null +++ b/types/account.go @@ -0,0 +1,27 @@ +package types + +import ( + "time" + + "github.com/google/uuid" +) + +// The Account holds money +type Account struct { + Id uuid.UUID + UserId uuid.UUID `db:"user_id"` + + // Custom Name of the account, e.g. "Bank", "Cash", "Credit Card" + Name string + + CurrentBalance int64 `db:"current_balance"` + LastTransaction *time.Time `db:"last_transaction"` + // The current precalculated value of: + // Account.Balance - [PiggyBank.Balance...] + OinkBalance int64 `db:"oink_balance"` + + CreatedAt time.Time `db:"created_at"` + CreatedBy uuid.UUID `db:"created_by"` + UpdatedAt *time.Time `db:"updated_at"` + UpdatedBy *uuid.UUID `db:"updated_by"` +} diff --git a/types/money.go b/types/money.go deleted file mode 100644 index 090bff9..0000000 --- a/types/money.go +++ /dev/null @@ -1,91 +0,0 @@ -package types - -import ( - "time" - - "github.com/google/uuid" -) - -// At the center of the application is the transaction. -// -// Every piece of data should be calculated based on transactions. This means potential calculation errors can be fixed later in time. -// -// If it becomes necessary to precalculate snapshots for performance reasons, this can be done in the future. But the transaction should always be the source of truth. -type Transaction struct { - Id uuid.UUID - UserId uuid.UUID - - AccountId uuid.UUID - // nil indicates that the transaction is not yet associated with a piggy bank - PiggyBankId *uuid.UUID - - // The internal transaction is amove between e.g. an account and a piggy bank to execute a savings plan - Internal bool - - // The value of the transacion. Negative for outgoing and positive for incoming - Value int64 - Timestamp time.Time - - Note string - - CreatedAt time.Time - // either "" or "system-" - CreatedBy uuid.UUID - UpdatedAt time.Time - UpdatedBy uuid.UUID -} - -// The Account holds money -type Account struct { - Id uuid.UUID - UserId uuid.UUID `db:"user_id"` - - // Custom Name of the account, e.g. "Bank", "Cash", "Credit Card" - Name string - - CurrentBalance int64 `db:"current_balance"` - LastTransaction *time.Time `db:"last_transaction"` - // The current precalculated value of: - // Account.Balance - [PiggyBank.Balance...] - OinkBalance int64 `db:"oink_balance"` - - CreatedAt time.Time `db:"created_at"` - CreatedBy uuid.UUID `db:"created_by"` - UpdatedAt *time.Time `db:"updated_at"` - UpdatedBy *uuid.UUID `db:"updated_by"` -} - -// The PiggyBank is a fictional account. The money it "holds" is actually in the Account -type PiggyBank struct { - Id uuid.UUID - UserId uuid.UUID - - AccountId uuid.UUID - Name string - - CurrentBalance int64 - - CreatedAt time.Time - CreatedBy uuid.UUID - UpdatedAt *time.Time - UpdatedBy *uuid.UUID -} - -// The SavingsPlan is applied every interval to the PiggyBank/Account as a transaction -type SavingsPlan struct { - Id uuid.UUID - UserId uuid.UUID - - PiggyBankId uuid.UUID - - MonthlySaving int64 - - ValidFrom time.Time - /// nil means it is valid indefinitely - ValidTo *time.Time - - CreatedAt time.Time - CreatedBy uuid.UUID - UpdatedAt *time.Time - UpdatedBy *uuid.UUID -} diff --git a/types/savings_plan.go b/types/savings_plan.go new file mode 100644 index 0000000..d4d5e09 --- /dev/null +++ b/types/savings_plan.go @@ -0,0 +1,26 @@ +package types + +import ( + "time" + + "github.com/google/uuid" +) + +// The SavingsPlan is applied every interval to the TreasureChest/Account as a transaction +type SavingsPlan struct { + Id uuid.UUID + UserId uuid.UUID `db:"user_id"` + + TreasureChestId uuid.UUID `db:"treasure_chest_id"` + + MonthlySaving int64 `db:"monthly_saving"` + + ValidFrom time.Time `db:"valid_from"` + /// nil means it is valid indefinitely + ValidTo *time.Time `db:"valid_to"` + + CreatedAt time.Time `db:"created_at"` + CreatedBy uuid.UUID `db:"created_by"` + UpdatedAt *time.Time `db:"updated_at"` + UpdatedBy *uuid.UUID `db:"updated_by"` +} diff --git a/types/transaction.go b/types/transaction.go new file mode 100644 index 0000000..1f22c0f --- /dev/null +++ b/types/transaction.go @@ -0,0 +1,37 @@ +package types + +import ( + "time" + + "github.com/google/uuid" +) + +// At the center of the application is the transaction. +// +// Every piece of data should be calculated based on transactions. +// This means potential calculation errors can be fixed later in time. +// +// If it becomes necessary to precalculate snapshots for performance reasons, this can be done in the future. +// But the transaction should always be the source of truth. +type Transaction struct { + Id uuid.UUID + UserId uuid.UUID `db:"user_id"` + + AccountId uuid.UUID `db:"account_id"` + // nil indicates that the transaction is not yet associated with a piggy bank + TreasureChestId *uuid.UUID `db:"treasure_chest_id"` + + // The internal transaction is amove between e.g. an account and a piggy bank to execute a savings plan + Internal bool + + // The value of the transacion. Negative for outgoing and positive for incoming + Value int64 + Timestamp time.Time + + Note string + + CreatedAt time.Time `db:"created_at"` + CreatedBy uuid.UUID `db:"created_by"` + UpdatedAt *time.Time `db:"updated_at"` + UpdatedBy *uuid.UUID `db:"updated_by"` +} diff --git a/types/treasure.go b/types/treasure.go new file mode 100644 index 0000000..32f491f --- /dev/null +++ b/types/treasure.go @@ -0,0 +1,24 @@ +package types + +import ( + "time" + + "github.com/google/uuid" +) + +// The TreasureChest is a fictional account. +// The money it "holds" is actually in the linked Account +type TreasureChest struct { + Id uuid.UUID + UserId uuid.UUID `db:"user_id"` + + AccountId uuid.UUID `db:"account_id"` + Name string + + CurrentBalance int64 `db:"current_balance"` + + CreatedAt time.Time `db:"created_at"` + CreatedBy uuid.UUID `db:"created_by"` + UpdatedAt *time.Time `db:"updated_at"` + UpdatedBy *uuid.UUID `db:"updated_by"` +} -- 2.49.1 From df022c90774cff2b80de6e472ba149f5e761798f Mon Sep 17 00:00:00 2001 From: Tim Wundenberg Date: Sat, 10 May 2025 23:27:03 +0200 Subject: [PATCH 2/2] feat(account): #49 add mertrics --- log/default.go | 2 +- service/account.go | 17 ++++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/log/default.go b/log/default.go index bf50f98..7ab6213 100644 --- a/log/default.go +++ b/log/default.go @@ -13,7 +13,7 @@ import ( var ( errorMetric = promauto.NewCounter( prometheus.CounterOpts{ - Name: "mefit_error_total", + Name: "spendsparrow_error_total", Help: "The total number of errors during processing", }, ) diff --git a/service/account.go b/service/account.go index 48ac0cd..443ff9b 100644 --- a/service/account.go +++ b/service/account.go @@ -9,10 +9,20 @@ import ( "spend-sparrow/types" "github.com/google/uuid" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" ) var ( safeInputRegex = regexp.MustCompile(`^[a-zA-Z0-9äöüß -]+$`) + + accountMetric = promauto.NewCounterVec( + prometheus.CounterOpts{ + Name: "spendsparrow_account_total", + Help: "The total of account operations", + }, + []string{"operation"}, + ) ) type Account interface { @@ -40,6 +50,8 @@ func NewAccountImpl(db db.Account, random Random, clock Clock, settings *types.S } func (s AccountImpl) Add(user *types.User, name string) (*types.Account, error) { + accountMetric.WithLabelValues("add").Inc() + if user == nil { return nil, ErrUnauthorized } @@ -84,6 +96,7 @@ func (s AccountImpl) Add(user *types.User, name string) (*types.Account, error) } func (s AccountImpl) Update(user *types.User, id uuid.UUID, name string) (*types.Account, error) { + accountMetric.WithLabelValues("update").Inc() if user == nil { return nil, ErrUnauthorized } @@ -114,6 +127,7 @@ func (s AccountImpl) Update(user *types.User, id uuid.UUID, name string) (*types } func (s AccountImpl) Get(user *types.User, id uuid.UUID) (*types.Account, error) { + accountMetric.WithLabelValues("get").Inc() if user == nil { return nil, ErrUnauthorized @@ -131,7 +145,7 @@ func (s AccountImpl) Get(user *types.User, id uuid.UUID) (*types.Account, error) } func (s AccountImpl) GetAll(user *types.User) ([]*types.Account, error) { - + accountMetric.WithLabelValues("get_all").Inc() if user == nil { return nil, ErrUnauthorized } @@ -145,6 +159,7 @@ func (s AccountImpl) GetAll(user *types.User) ([]*types.Account, error) { } func (s AccountImpl) Delete(user *types.User, id uuid.UUID) error { + accountMetric.WithLabelValues("delete").Inc() if user == nil { return ErrUnauthorized } -- 2.49.1