expenses

package module
v0.3.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 25, 2021 License: MIT Imports: 8 Imported by: 0

README

expenses

An oversimplified library to work with expenses.

The library is implemented with two principles in mind:

  • Records must be treated as collection of immutable items (except for their metadata) and can only be created;
  • Records must be available for listing.

The library merely hints to respect these principles and does not limit or enforce application behaviour.

$ go test -cover
PASS
coverage: 90.8% of statements
ok  	github.com/lexndru/expenses	0.705s

MIT License

Documentation

Overview

Copyright (c) 2021 Alexandru Catrina

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Index

Constants

View Source
const (
	ModVersion = "0.3.2"
	DateFormat = "Mon 02 Jan 2006" // Layout used instead of "d m Y" abbr
)

Variables

This section is empty.

Functions

func FromJson

func FromJson(src []byte, into interface{}) error

FromJson is a tiny helper function to deserialize a JSON payload into one of the registry key components (Actors, Labels, Transactions)

func Install

func Install(db *gorm.DB) error

Install is a helper function to create and migrate the required tables on a supported database. Failure to install returns errors and must be handled by the caller

func NewPullRequest

func NewPullRequest(reg Registry, ctx PullContext) (out []byte, err error)

NewPullRequest is promoted as the primary entrypoint to use/handle supported registry component (Actors, Labels, Transactions) because of it's wrapped on the repetitive pipeline to list existing records; obtain a copy of the items in the form of Golang structs and JSON as well. Upon failure an error yields instead and none or incomplete records are actually being *pulled*

func NewPushRequest

func NewPushRequest(reg Registry, ctx PushContext) (out []byte, err error)

NewPushRequest is promoted as the primary entrypoint to use/handle supported registry component (Actors, Labels, Transactions) because of it's wrapped on the repetitive pipeline to create new records and obtain a copy of the items in the form of Golang structs and JSON as well. Upon failure an error yields instead and none or incomplete records are actually being *pushed*

func ToJson

func ToJson(src interface{}) ([]byte, error)

ToJson is a tiny helper function to serialize into JSON any supported registry key components (this is the inverse of FromJson)

func Uninstall

func Uninstall(db *gorm.DB) error

Uninstall is a helper function to delete previous installments. Upon failure it returns errors that must be handled by the caller

Types

type Actor

type Actor struct {
	Name      string    `json:"name" gorm:"type: varchar(100); primaryKey"`
	Flags     uint16    `json:"flags" gorm:"not null"`
	Headers   string    `json:"headers" gorm:"type: text; not null"`
	CreatedAt time.Time `json:"-" gorm:"autoCreateTime"`
	UpdatedAt time.Time `json:"-" gorm:"autoUpdateTime"`
}

Actor is one of the key components of the expenses module. An actor is an abstraction of any participant in a transaction. Currenly its use is to differenciate between *senders* and *receivers*

func NewActor

func NewActor(name string) Actor

NewActor is an idiomatic constructor for the Actor entity. This method doesn't handle meta fields such as Flags or Headers

func (*Actor) BeforeCreate

func (a *Actor) BeforeCreate(tx *gorm.DB) (err error)

BeforeCreate hook from GORM to check if actors has valid name

func (*Actor) String

func (a *Actor) String() string

String representation of an *actor* (any actor; this output won't tell whether it's a sender or a receiver because this role exists only in the context of a transaction)

type Actors

type Actors []Actor

Actors is a registry-type that represents a collection of its appropriate *Actor* entities

func (*Actors) Pull

func (a *Actors) Pull(ctx PullContext) error

Pull enables to read actors from registry. The results are always sorted by their name

func (*Actors) Push

func (a *Actors) Push(ctx PushContext) error

Push enables to write new actors into registry or updates the fields of the existing ones if a *name* conflict occurs

type Details

type Details struct {
	UUID            *string   `json:"-" gorm:"type: varchar(36); primaryKey"`
	TransactionUUID string    `json:"-" gorm:"not null"`
	LabelName       string    `json:"label" gorm:"not null"`
	Amount          int64     `json:"amount" gorm:"not null"`
	Flags           uint16    `json:"flags" gorm:"not null"`
	Headers         string    `json:"headers" gorm:"type: text; not null"`
	CreatedAt       time.Time `json:"-" gorm:"autoCreateTime"`
	UpdatedAt       time.Time `json:"-" gorm:"autoUpdateTime"`

	Transaction *Transaction `json:"-" gorm:"foreignKey: TransactionUUID"`
	Label       *Label       `json:"-" gorm:"foreignKey: LabelName; constraint: OnUpdate:CASCADE"`
}

Details is an adjacent component of the expenses module to support the *amount* breakdown of a Transaction into multiple records individually labeled.

This is the *only* entity that's created indirectly from a Transaction and cannot have its fields updated in any way

func (*Details) BeforeCreate

func (d *Details) BeforeCreate(tx *gorm.DB) (err error)

BeforeCreate hook from GORM to generate an UUID just before creating the new entry for the transaction details

func (*Details) String

func (d *Details) String() string

String representation of transaction's detailed entity

type Label

type Label struct {
	Name       string     `json:"name" gorm:"type: varchar(100); primaryKey"`
	ParentName NullString `json:"parent" gorm:"type: varchar(100)"`
	Flags      uint16     `json:"flags" gorm:"not null"`
	Headers    string     `json:"headers" gorm:"type: text; not null"`
	CreatedAt  time.Time  `json:"-" gorm:"autoCreateTime"`
	UpdatedAt  time.Time  `json:"-" gorm:"autoUpdateTime"`

	Parent *Label `json:"-" gorm:"foreignKey: ParentName"`
}

Label is another key component of the expenses module. A label is an user-defined entity used to classify transactions through meta information. The label has a tree-like structure where any entity can be the parent of any other entities, while the root entity is always present with NULL value for parent field

func NewLabel

func NewLabel(name string, parent *Label) Label

NewLabel is an idiomatic constructor for the Label entity. This method doesn't handle meta fields such as Flags or Headers

func (*Label) BeforeCreate

func (lb *Label) BeforeCreate(tx *gorm.DB) (err error)

BeforeCreate hook from GORM to correctly attach the parent name from the relationship, if any; and validate provided name

func (*Label) String

func (lb *Label) String() string

String representation of a *label* (any label)

type Labels

type Labels []Label

Labels is a registry-type that represents a collection of its appropriate *Label* entities

func (*Labels) Pull

func (l *Labels) Pull(ctx PullContext) error

Pull enables to read labels from registry. The results are always sorted by their name and contain the parents of the already retrieved labels as well

func (*Labels) Push

func (l *Labels) Push(ctx PushContext) error

Push enables to write new labels into registry or updates the fields of the existing ones if a *name* conflict occurs. Each label can have a parent label to link with

type NullString

type NullString struct {
	sql.NullString
}

NullString is a compatible SQL and JSON structure, mostly added to support the tree structure of Label's optional Parent

func (NullString) MarshalJSON

func (ns NullString) MarshalJSON() ([]byte, error)

MarshalJSON to properly support NULL conversion (see above)

func (*NullString) UnmarshalJSON

func (ns *NullString) UnmarshalJSON(b []byte) error

UnmarshalJSON to properly support NULL conversion (the problem was actually sql.NullString not having this conversion)

type PullContext

type PullContext struct {

	// Storage is mainly Maria/MySQL with little support for SQLite
	Storage *gorm.DB

	// Limit is the equivalent of SQL LIMIT statement. By default it
	// isn't set on query because of the default zero value
	Limit int

	// Offset is the equivalent of SQL OFFSET statement on query. It
	// is similar to Limit, by default it isn't set
	Offset int
}

PullContext is complement with PushContext (see above)

The use of PULL is to retrieve records

type PushContext

type PushContext struct {

	// Storage is mainly Maria/MySQL with little support for SQLite
	Storage *gorm.DB

	// BatchSize must be a positive number above zero, otherwise it
	// will fail to write
	BatchSize int

	// JustAppend is mostly used internally to upsert only and don't
	// propage updates to all fields
	JustAppend bool
}

PushContext is a thin wrapper to "explain" to an entity what needs to be pushed into registry since the registry it's not aware of the underlying persistence layer

The use of PUSH is to create records through upsert and update fields on conflict

type Registry

type Registry interface {
	Push(PushContext) error
	Pull(PullContext) error
}

Registry is defined as an unified simplistic API developed to interact with an underlaying persistence layer via only two routes. The concept of the expenses module is visualy defined by its component parts below

(Participants)
    Actor        Label (User defined label to identify scope)
      |            |
      \           /|
       Transaction |
            \_    /
               Details (Transaction breakdown for the amount)

A *Transaction* is a registry entry that demonstrates an exchange between two participants (Actors) for a given amount (base currency) with one or many declared scopes (Transaction Label and Details Labels)

Only *collections* of records are considered valid candidates to implement this interface since the concept of the registry works with sets of items

type Transaction

type Transaction struct {
	UUID         *string   `json:"uuid,omitempty" gorm:"type: varchar(36); primaryKey"`
	Date         time.Time `json:"date" gorm:"type: date; index; not null"`
	Amount       int64     `json:"amount" gorm:"not null"`
	LabelName    string    `json:"label" gorm:"not null"`
	SenderName   string    `json:"sender" gorm:"not null"`
	ReceiverName string    `json:"receiver" gorm:"not null"`
	Signature    string    `json:"signature" gorm:"type: varchar(36); index; not null"`
	Flags        uint16    `json:"flags" gorm:"not null"`
	Headers      string    `json:"headers" gorm:"type: text; not null"`
	CreatedAt    time.Time `json:"-" gorm:"autoCreateTime"`
	UpdatedAt    time.Time `json:"-" gorm:"autoUpdateTime"`

	Label    *Label `json:"-" gorm:"foreignKey: LabelName; constraint: OnUpdate:CASCADE"`
	Sender   *Actor `json:"-" gorm:"foreignKey: SenderName; constraint: OnUpdate:CASCADE"`
	Receiver *Actor `json:"-" gorm:"foreignKey: ReceiverName; constraint: OnUpdate:CASCADE"`

	Details []*Details `json:"details" gorm:"foreignKey: TransactionUUID; references: UUID"`
}

Transaction *is* the key component of the expenses module which bounds together foreign Actors and Labels. Any transaction entity is actually the equivalent of a real-world transaction between two parties, namely a Sender and a Receiver, which are both Actors. Meta information about the transaction itself is made through user-defined Labels

A transaction cannot change its *date* and payment *amount* afterwards but it allows to update its Actors and Labels, as long as they exists; and most importantly: the *amount* field is used to interpret the type of the transaction as a binary operation (IN > 0 otherwise OUT)

func NewTransaction

func NewTransaction(d time.Time, a int64, lb Label, tx, rx Actor, ls map[Label]int64, z string) Transaction

NewTransaction is the primary idiomatic constructor for the Transaction entity from which all other records can derive. Transaction details can be omitted by providing nil map. This method doesn't handle meta fields such as Flags or Headers

func (*Transaction) BeforeCreate

func (t *Transaction) BeforeCreate(tx *gorm.DB) (err error)

BeforeCreate hook from GORM to generate an UUID just before creating the new entry for the transaction. The use of UUID as string instead of binary is due to JSON (un)marshal and portability over ASCII only communication channels

This method is responsible for constraints check upon amount details preventing the introduction of incomplete or corrupted transactions

func (*Transaction) String

func (t *Transaction) String() string

String representation of a transaction with all its fields and ahead of time resolution of the relationship between components. An output may contain transaction details as well

type Transactions

type Transactions []Transaction

Transactions is a registry-type that represents a collection of its appropriate *Transaction* entities. This is the primary registry of the expenses module

func (*Transactions) Pull

func (t *Transactions) Pull(ctx PullContext) error

Pull from registry automatically resolves the relationship between these three components (Actors, Labels, Details) and the results are sorted by the descending date & amount of the real-world transaction authorization

func (*Transactions) Push

func (t *Transactions) Push(ctx PushContext) error

Push into registry *must* always succeed to write a list of transactions in the persistent layer, whether it requires additional Actors/Labels to be written before the actual commit or to add details after the commit

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL