49-accounts #63
@@ -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",
|
||||
},
|
||||
)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
27
types/account.go
Normal file
27
types/account.go
Normal file
@@ -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"`
|
||||
}
|
||||
@@ -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 "<username>" or "system-<subsystem>"
|
||||
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
|
||||
}
|
||||
26
types/savings_plan.go
Normal file
26
types/savings_plan.go
Normal file
@@ -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"`
|
||||
}
|
||||
37
types/transaction.go
Normal file
37
types/transaction.go
Normal file
@@ -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"`
|
||||
}
|
||||
24
types/treasure.go
Normal file
24
types/treasure.go
Normal file
@@ -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"`
|
||||
}
|
||||
Reference in New Issue
Block a user