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 |   - touch $GOPATH/src/github.com/aclindsa/moneygo/internal/handlers/cusip_list.csv | ||||||
|   # Build and test MoneyGo |   # Build and test MoneyGo | ||||||
|   - go generate -v github.com/aclindsa/moneygo/internal/handlers |   - 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,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 -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=config_coverage.out github.com/aclindsa/moneygo/internal/config | ||||||
|  |  | ||||||
| # Report the test coverage | # Report the test coverage | ||||||
| after_script: | after_script: | ||||||
|   | |||||||
| @@ -5,7 +5,6 @@ import ( | |||||||
| 	"github.com/aclindsa/moneygo/internal/store" | 	"github.com/aclindsa/moneygo/internal/store" | ||||||
| 	"log" | 	"log" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"time" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func CreatePriceIfNotExist(tx store.Tx, price *models.Price) error { | 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 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 { | func PriceHandler(r *http.Request, context *Context, user *models.User, securityid int64) ResponseWriterWriter { | ||||||
| 	security, err := context.Tx.GetSecurity(securityid, user.UserId) | 	security, err := context.Tx.GetSecurity(securityid, user.UserId) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|   | |||||||
| @@ -1,104 +1,23 @@ | |||||||
| package handlers | package handlers | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"context" |  | ||||||
| 	"errors" |  | ||||||
| 	"fmt" |  | ||||||
| 	"github.com/aclindsa/moneygo/internal/models" | 	"github.com/aclindsa/moneygo/internal/models" | ||||||
|  | 	"github.com/aclindsa/moneygo/internal/reports" | ||||||
| 	"github.com/aclindsa/moneygo/internal/store" | 	"github.com/aclindsa/moneygo/internal/store" | ||||||
| 	"github.com/yuin/gopher-lua" |  | ||||||
| 	"log" | 	"log" | ||||||
| 	"net/http" | 	"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 { | func ReportTabulationHandler(tx store.Tx, r *http.Request, user *models.User, reportid int64) ResponseWriterWriter { | ||||||
| 	report, err := tx.GetReport(reportid, user.UserId) | 	report, err := tx.GetReport(reportid, user.UserId) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return NewError(3 /*Invalid Request*/) | 		return NewError(3 /*Invalid Request*/) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	tabulation, err := runReport(tx, user, report) | 	tabulation, err := reports.RunReport(tx, user, report) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		// TODO handle different failure cases differently | 		// TODO handle different failure cases differently | ||||||
| 		log.Print("runReport returned:", err) | 		log.Print("reports.RunReport returned:", err) | ||||||
| 		return NewError(3 /*Invalid Request*/) | 		return NewError(3 /*Invalid Request*/) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -182,20 +182,6 @@ func TransactionHandler(r *http.Request, context *Context) ResponseWriterWriter | |||||||
| 	return NewError(3 /*Invalid Request*/) | 	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 | // Return only those transactions which have at least one split pertaining to | ||||||
| // an account | // an account | ||||||
| func AccountTransactionsHandler(context *Context, r *http.Request, user *models.User, accountid int64) ResponseWriterWriter { | func AccountTransactionsHandler(context *Context, r *http.Request, user *models.User, accountid int64) ResponseWriterWriter { | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| package handlers | package reports | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
| @@ -6,6 +6,7 @@ import ( | |||||||
| 	"github.com/aclindsa/moneygo/internal/models" | 	"github.com/aclindsa/moneygo/internal/models" | ||||||
| 	"github.com/aclindsa/moneygo/internal/store" | 	"github.com/aclindsa/moneygo/internal/store" | ||||||
| 	"github.com/yuin/gopher-lua" | 	"github.com/yuin/gopher-lua" | ||||||
|  | 	"math/big" | ||||||
| 	"strings" | 	"strings" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| @@ -146,6 +147,20 @@ func luaAccount__index(L *lua.LState) int { | |||||||
| 	return 1 | 	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 { | func luaAccountBalance(L *lua.LState) int { | ||||||
| 	a := luaCheckAccount(L, 1) | 	a := luaCheckAccount(L, 1) | ||||||
| 
 | 
 | ||||||
| @@ -181,7 +196,7 @@ func luaAccountBalance(L *lua.LState) int { | |||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		panic("Failed to fetch splits for account:" + err.Error()) | 		panic("Failed to fetch splits for account:" + err.Error()) | ||||||
| 	} | 	} | ||||||
| 	rat, err := BalanceFromSplits(splits) | 	rat, err := balanceFromSplits(splits) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		panic("Failed to calculate balance for account:" + err.Error()) | 		panic("Failed to calculate balance for account:" + err.Error()) | ||||||
| 	} | 	} | ||||||
| @@ -1,4 +1,4 @@ | |||||||
| package handlers | package reports | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"github.com/aclindsa/moneygo/internal/models" | 	"github.com/aclindsa/moneygo/internal/models" | ||||||
| @@ -1,4 +1,4 @@ | |||||||
| package handlers | package reports | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"github.com/yuin/gopher-lua" | 	"github.com/yuin/gopher-lua" | ||||||
| @@ -1,4 +1,4 @@ | |||||||
| package handlers | package reports | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"github.com/aclindsa/moneygo/internal/models" | 	"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 ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
| @@ -6,6 +6,7 @@ import ( | |||||||
| 	"github.com/aclindsa/moneygo/internal/models" | 	"github.com/aclindsa/moneygo/internal/models" | ||||||
| 	"github.com/aclindsa/moneygo/internal/store" | 	"github.com/aclindsa/moneygo/internal/store" | ||||||
| 	"github.com/yuin/gopher-lua" | 	"github.com/yuin/gopher-lua" | ||||||
|  | 	"time" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| const luaSecurityTypeName = "security" | const luaSecurityTypeName = "security" | ||||||
| @@ -153,6 +154,27 @@ func luaSecurity__index(L *lua.LState) int { | |||||||
| 	return 1 | 	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 { | func luaClosestPrice(L *lua.LState) int { | ||||||
| 	s := luaCheckSecurity(L, 1) | 	s := luaCheckSecurity(L, 1) | ||||||
| 	c := luaCheckSecurity(L, 2) | 	c := luaCheckSecurity(L, 2) | ||||||
| @@ -164,7 +186,7 @@ func luaClosestPrice(L *lua.LState) int { | |||||||
| 		panic("Couldn't find tx in lua's Context") | 		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 { | 	if err != nil { | ||||||
| 		L.Push(lua.LNil) | 		L.Push(lua.LNil) | ||||||
| 	} else { | 	} else { | ||||||
| @@ -1,4 +1,4 @@ | |||||||
| package handlers | package reports | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"github.com/aclindsa/moneygo/internal/models" | 	"github.com/aclindsa/moneygo/internal/models" | ||||||
		Reference in New Issue
	
	Block a user