mirror of
				https://github.com/aclindsa/moneygo.git
				synced 2025-10-31 09:53:27 -04:00 
			
		
		
		
	Split Lua reports into own package
This commit is contained in:
		| @@ -59,8 +59,8 @@ script: | ||||
|   - touch $GOPATH/src/github.com/aclindsa/moneygo/internal/handlers/cusip_list.csv | ||||
|   # Build and test MoneyGo | ||||
|   - go generate -v github.com/aclindsa/moneygo/internal/handlers | ||||
|   - go test -v -covermode=count -coverpkg github.com/aclindsa/moneygo/internal/config,github.com/aclindsa/moneygo/internal/handlers,github.com/aclindsa/moneygo/internal/models,github.com/aclindsa/moneygo/internal/store,github.com/aclindsa/moneygo/internal/store/db -coverprofile=integration_coverage.out github.com/aclindsa/moneygo/internal/integration | ||||
|   - go test -v -covermode=count -coverpkg github.com/aclindsa/moneygo/internal/config,github.com/aclindsa/moneygo/internal/handlers,github.com/aclindsa/moneygo/internal/models,github.com/aclindsa/moneygo/internal/store,github.com/aclindsa/moneygo/internal/store/db -coverprofile=config_coverage.out github.com/aclindsa/moneygo/internal/config | ||||
|   - go test -v -covermode=count -coverpkg github.com/aclindsa/moneygo/internal/config,github.com/aclindsa/moneygo/internal/handlers,github.com/aclindsa/moneygo/internal/models,github.com/aclindsa/moneygo/internal/store,github.com/aclindsa/moneygo/internal/store/db,github.com/aclindsa/moneygo/internal/reports -coverprofile=integration_coverage.out github.com/aclindsa/moneygo/internal/integration | ||||
|   - go test -v -covermode=count -coverpkg github.com/aclindsa/moneygo/internal/config,github.com/aclindsa/moneygo/internal/handlers,github.com/aclindsa/moneygo/internal/models,github.com/aclindsa/moneygo/internal/store,github.com/aclindsa/moneygo/internal/store/db,github.com/aclindsa/moneygo/internal/reports -coverprofile=config_coverage.out github.com/aclindsa/moneygo/internal/config | ||||
|  | ||||
| # Report the test coverage | ||||
| after_script: | ||||
|   | ||||
| @@ -5,7 +5,6 @@ import ( | ||||
| 	"github.com/aclindsa/moneygo/internal/store" | ||||
| 	"log" | ||||
| 	"net/http" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| func CreatePriceIfNotExist(tx store.Tx, price *models.Price) error { | ||||
| @@ -33,27 +32,6 @@ func CreatePriceIfNotExist(tx store.Tx, price *models.Price) error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Return the price for security in currency closest to date | ||||
| func GetClosestPrice(tx store.Tx, security, currency *models.Security, date *time.Time) (*models.Price, error) { | ||||
| 	earliest, _ := tx.GetEarliestPrice(security, currency, date) | ||||
| 	latest, err := tx.GetLatestPrice(security, currency, date) | ||||
|  | ||||
| 	// Return early if either earliest or latest are invalid | ||||
| 	if earliest == nil { | ||||
| 		return latest, err | ||||
| 	} else if err != nil { | ||||
| 		return earliest, nil | ||||
| 	} | ||||
|  | ||||
| 	howlate := earliest.Date.Sub(*date) | ||||
| 	howearly := date.Sub(latest.Date) | ||||
| 	if howearly < howlate { | ||||
| 		return latest, nil | ||||
| 	} else { | ||||
| 		return earliest, nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func PriceHandler(r *http.Request, context *Context, user *models.User, securityid int64) ResponseWriterWriter { | ||||
| 	security, err := context.Tx.GetSecurity(securityid, user.UserId) | ||||
| 	if err != nil { | ||||
|   | ||||
| @@ -1,104 +1,23 @@ | ||||
| package handlers | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"github.com/aclindsa/moneygo/internal/models" | ||||
| 	"github.com/aclindsa/moneygo/internal/reports" | ||||
| 	"github.com/aclindsa/moneygo/internal/store" | ||||
| 	"github.com/yuin/gopher-lua" | ||||
| 	"log" | ||||
| 	"net/http" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| //type and value to store user in lua's Context | ||||
| type key int | ||||
|  | ||||
| const ( | ||||
| 	userContextKey key = iota | ||||
| 	accountsContextKey | ||||
| 	securitiesContextKey | ||||
| 	balanceContextKey | ||||
| 	dbContextKey | ||||
| ) | ||||
|  | ||||
| const luaTimeoutSeconds time.Duration = 30 // maximum time a lua request can run for | ||||
|  | ||||
| func runReport(tx store.Tx, user *models.User, report *models.Report) (*models.Tabulation, error) { | ||||
| 	// Create a new LState without opening the default libs for security | ||||
| 	L := lua.NewState(lua.Options{SkipOpenLibs: true}) | ||||
| 	defer L.Close() | ||||
|  | ||||
| 	// Create a new context holding the current user with a timeout | ||||
| 	ctx := context.WithValue(context.Background(), userContextKey, user) | ||||
| 	ctx = context.WithValue(ctx, dbContextKey, tx) | ||||
| 	ctx, cancel := context.WithTimeout(ctx, luaTimeoutSeconds*time.Second) | ||||
| 	defer cancel() | ||||
| 	L.SetContext(ctx) | ||||
|  | ||||
| 	for _, pair := range []struct { | ||||
| 		n string | ||||
| 		f lua.LGFunction | ||||
| 	}{ | ||||
| 		{lua.LoadLibName, lua.OpenPackage}, // Must be first | ||||
| 		{lua.BaseLibName, lua.OpenBase}, | ||||
| 		{lua.TabLibName, lua.OpenTable}, | ||||
| 		{lua.StringLibName, lua.OpenString}, | ||||
| 		{lua.MathLibName, lua.OpenMath}, | ||||
| 	} { | ||||
| 		if err := L.CallByParam(lua.P{ | ||||
| 			Fn:      L.NewFunction(pair.f), | ||||
| 			NRet:    0, | ||||
| 			Protect: true, | ||||
| 		}, lua.LString(pair.n)); err != nil { | ||||
| 			return nil, errors.New("Error initializing Lua packages") | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	luaRegisterAccounts(L) | ||||
| 	luaRegisterSecurities(L) | ||||
| 	luaRegisterBalances(L) | ||||
| 	luaRegisterDates(L) | ||||
| 	luaRegisterTabulations(L) | ||||
| 	luaRegisterPrices(L) | ||||
|  | ||||
| 	err := L.DoString(report.Lua) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	if err := L.CallByParam(lua.P{ | ||||
| 		Fn:      L.GetGlobal("generate"), | ||||
| 		NRet:    1, | ||||
| 		Protect: true, | ||||
| 	}); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	value := L.Get(-1) | ||||
| 	if ud, ok := value.(*lua.LUserData); ok { | ||||
| 		if tabulation, ok := ud.Value.(*models.Tabulation); ok { | ||||
| 			return tabulation, nil | ||||
| 		} else { | ||||
| 			return nil, fmt.Errorf("generate() for %s (Id: %d) didn't return a tabulation", report.Name, report.ReportId) | ||||
| 		} | ||||
| 	} else { | ||||
| 		return nil, fmt.Errorf("generate() for %s (Id: %d) didn't even return LUserData", report.Name, report.ReportId) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func ReportTabulationHandler(tx store.Tx, r *http.Request, user *models.User, reportid int64) ResponseWriterWriter { | ||||
| 	report, err := tx.GetReport(reportid, user.UserId) | ||||
| 	if err != nil { | ||||
| 		return NewError(3 /*Invalid Request*/) | ||||
| 	} | ||||
|  | ||||
| 	tabulation, err := runReport(tx, user, report) | ||||
| 	tabulation, err := reports.RunReport(tx, user, report) | ||||
| 	if err != nil { | ||||
| 		// TODO handle different failure cases differently | ||||
| 		log.Print("runReport returned:", err) | ||||
| 		log.Print("reports.RunReport returned:", err) | ||||
| 		return NewError(3 /*Invalid Request*/) | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -182,20 +182,6 @@ func TransactionHandler(r *http.Request, context *Context) ResponseWriterWriter | ||||
| 	return NewError(3 /*Invalid Request*/) | ||||
| } | ||||
|  | ||||
| func BalanceFromSplits(splits *[]*models.Split) (*big.Rat, error) { | ||||
| 	var balance, tmp big.Rat | ||||
| 	for _, s := range *splits { | ||||
| 		rat_amount, err := models.GetBigAmount(s.Amount) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		tmp.Add(&balance, rat_amount) | ||||
| 		balance.Set(&tmp) | ||||
| 	} | ||||
|  | ||||
| 	return &balance, nil | ||||
| } | ||||
|  | ||||
| // Return only those transactions which have at least one split pertaining to | ||||
| // an account | ||||
| func AccountTransactionsHandler(context *Context, r *http.Request, user *models.User, accountid int64) ResponseWriterWriter { | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| package handlers | ||||
| package reports | ||||
| 
 | ||||
| import ( | ||||
| 	"context" | ||||
| @@ -6,6 +6,7 @@ import ( | ||||
| 	"github.com/aclindsa/moneygo/internal/models" | ||||
| 	"github.com/aclindsa/moneygo/internal/store" | ||||
| 	"github.com/yuin/gopher-lua" | ||||
| 	"math/big" | ||||
| 	"strings" | ||||
| ) | ||||
| 
 | ||||
| @@ -146,6 +147,20 @@ func luaAccount__index(L *lua.LState) int { | ||||
| 	return 1 | ||||
| } | ||||
| 
 | ||||
| func balanceFromSplits(splits *[]*models.Split) (*big.Rat, error) { | ||||
| 	var balance, tmp big.Rat | ||||
| 	for _, s := range *splits { | ||||
| 		rat_amount, err := models.GetBigAmount(s.Amount) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		tmp.Add(&balance, rat_amount) | ||||
| 		balance.Set(&tmp) | ||||
| 	} | ||||
| 
 | ||||
| 	return &balance, nil | ||||
| } | ||||
| 
 | ||||
| func luaAccountBalance(L *lua.LState) int { | ||||
| 	a := luaCheckAccount(L, 1) | ||||
| 
 | ||||
| @@ -181,7 +196,7 @@ func luaAccountBalance(L *lua.LState) int { | ||||
| 	if err != nil { | ||||
| 		panic("Failed to fetch splits for account:" + err.Error()) | ||||
| 	} | ||||
| 	rat, err := BalanceFromSplits(splits) | ||||
| 	rat, err := balanceFromSplits(splits) | ||||
| 	if err != nil { | ||||
| 		panic("Failed to calculate balance for account:" + err.Error()) | ||||
| 	} | ||||
| @@ -1,4 +1,4 @@ | ||||
| package handlers | ||||
| package reports | ||||
| 
 | ||||
| import ( | ||||
| 	"github.com/aclindsa/moneygo/internal/models" | ||||
| @@ -1,4 +1,4 @@ | ||||
| package handlers | ||||
| package reports | ||||
| 
 | ||||
| import ( | ||||
| 	"github.com/yuin/gopher-lua" | ||||
| @@ -1,4 +1,4 @@ | ||||
| package handlers | ||||
| package reports | ||||
| 
 | ||||
| import ( | ||||
| 	"github.com/aclindsa/moneygo/internal/models" | ||||
							
								
								
									
										88
									
								
								internal/reports/reports.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								internal/reports/reports.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,88 @@ | ||||
| package reports | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"github.com/aclindsa/moneygo/internal/models" | ||||
| 	"github.com/aclindsa/moneygo/internal/store" | ||||
| 	"github.com/yuin/gopher-lua" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| //type and value to store user in lua's Context | ||||
| type key int | ||||
|  | ||||
| const ( | ||||
| 	userContextKey key = iota | ||||
| 	accountsContextKey | ||||
| 	securitiesContextKey | ||||
| 	balanceContextKey | ||||
| 	dbContextKey | ||||
| ) | ||||
|  | ||||
| const luaTimeoutSeconds time.Duration = 30 // maximum time a lua request can run for | ||||
|  | ||||
| func RunReport(tx store.Tx, user *models.User, report *models.Report) (*models.Tabulation, error) { | ||||
| 	// Create a new LState without opening the default libs for security | ||||
| 	L := lua.NewState(lua.Options{SkipOpenLibs: true}) | ||||
| 	defer L.Close() | ||||
|  | ||||
| 	// Create a new context holding the current user with a timeout | ||||
| 	ctx := context.WithValue(context.Background(), userContextKey, user) | ||||
| 	ctx = context.WithValue(ctx, dbContextKey, tx) | ||||
| 	ctx, cancel := context.WithTimeout(ctx, luaTimeoutSeconds*time.Second) | ||||
| 	defer cancel() | ||||
| 	L.SetContext(ctx) | ||||
|  | ||||
| 	for _, pair := range []struct { | ||||
| 		n string | ||||
| 		f lua.LGFunction | ||||
| 	}{ | ||||
| 		{lua.LoadLibName, lua.OpenPackage}, // Must be first | ||||
| 		{lua.BaseLibName, lua.OpenBase}, | ||||
| 		{lua.TabLibName, lua.OpenTable}, | ||||
| 		{lua.StringLibName, lua.OpenString}, | ||||
| 		{lua.MathLibName, lua.OpenMath}, | ||||
| 	} { | ||||
| 		if err := L.CallByParam(lua.P{ | ||||
| 			Fn:      L.NewFunction(pair.f), | ||||
| 			NRet:    0, | ||||
| 			Protect: true, | ||||
| 		}, lua.LString(pair.n)); err != nil { | ||||
| 			return nil, errors.New("Error initializing Lua packages") | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	luaRegisterAccounts(L) | ||||
| 	luaRegisterSecurities(L) | ||||
| 	luaRegisterBalances(L) | ||||
| 	luaRegisterDates(L) | ||||
| 	luaRegisterTabulations(L) | ||||
| 	luaRegisterPrices(L) | ||||
|  | ||||
| 	err := L.DoString(report.Lua) | ||||
|  | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	if err := L.CallByParam(lua.P{ | ||||
| 		Fn:      L.GetGlobal("generate"), | ||||
| 		NRet:    1, | ||||
| 		Protect: true, | ||||
| 	}); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	value := L.Get(-1) | ||||
| 	if ud, ok := value.(*lua.LUserData); ok { | ||||
| 		if tabulation, ok := ud.Value.(*models.Tabulation); ok { | ||||
| 			return tabulation, nil | ||||
| 		} else { | ||||
| 			return nil, fmt.Errorf("generate() for %s (Id: %d) didn't return a tabulation", report.Name, report.ReportId) | ||||
| 		} | ||||
| 	} else { | ||||
| 		return nil, fmt.Errorf("generate() for %s (Id: %d) didn't even return LUserData", report.Name, report.ReportId) | ||||
| 	} | ||||
| } | ||||
| @@ -1,4 +1,4 @@ | ||||
| package handlers | ||||
| package reports | ||||
| 
 | ||||
| import ( | ||||
| 	"context" | ||||
| @@ -6,6 +6,7 @@ import ( | ||||
| 	"github.com/aclindsa/moneygo/internal/models" | ||||
| 	"github.com/aclindsa/moneygo/internal/store" | ||||
| 	"github.com/yuin/gopher-lua" | ||||
| 	"time" | ||||
| ) | ||||
| 
 | ||||
| const luaSecurityTypeName = "security" | ||||
| @@ -153,6 +154,27 @@ func luaSecurity__index(L *lua.LState) int { | ||||
| 	return 1 | ||||
| } | ||||
| 
 | ||||
| // Return the price for security in currency closest to date | ||||
| func getClosestPrice(tx store.Tx, security, currency *models.Security, date *time.Time) (*models.Price, error) { | ||||
| 	earliest, _ := tx.GetEarliestPrice(security, currency, date) | ||||
| 	latest, err := tx.GetLatestPrice(security, currency, date) | ||||
| 
 | ||||
| 	// Return early if either earliest or latest are invalid | ||||
| 	if earliest == nil { | ||||
| 		return latest, err | ||||
| 	} else if err != nil { | ||||
| 		return earliest, nil | ||||
| 	} | ||||
| 
 | ||||
| 	howlate := earliest.Date.Sub(*date) | ||||
| 	howearly := date.Sub(latest.Date) | ||||
| 	if howearly < howlate { | ||||
| 		return latest, nil | ||||
| 	} else { | ||||
| 		return earliest, nil | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func luaClosestPrice(L *lua.LState) int { | ||||
| 	s := luaCheckSecurity(L, 1) | ||||
| 	c := luaCheckSecurity(L, 2) | ||||
| @@ -164,7 +186,7 @@ func luaClosestPrice(L *lua.LState) int { | ||||
| 		panic("Couldn't find tx in lua's Context") | ||||
| 	} | ||||
| 
 | ||||
| 	p, err := GetClosestPrice(tx, s, c, date) | ||||
| 	p, err := getClosestPrice(tx, s, c, date) | ||||
| 	if err != nil { | ||||
| 		L.Push(lua.LNil) | ||||
| 	} else { | ||||
| @@ -1,4 +1,4 @@ | ||||
| package handlers | ||||
| package reports | ||||
| 
 | ||||
| import ( | ||||
| 	"github.com/aclindsa/moneygo/internal/models" | ||||
		Reference in New Issue
	
	Block a user