ynab-portfolio-monitor/main.go

108 lines
3.2 KiB
Go

package main
import (
"log"
"os"
"sync"
"text/template"
"time"
"deadbeef.codes/steven/ynab-portfolio-monitor/ynab"
)
var (
configuredProviders []AccountProvider // Any account providers that are successfully configured get added to this slice
ynabClient *ynab.Client // YNAB HTTP client
lastRefresh time.Time // Timestamp that last data refresh ran - used for rate limiting
refreshRunning *sync.Mutex // True if a refresh is currently running
t *template.Template // HTML templates in the templates directory
)
// Called at program startup or if SIGHUP is received
func init() {
log.Printf("ynab-portfolio-monitor init")
// Load mandatory application configuration from environment variables
envVars := make(map[string]string)
envVars["ynab_secret"] = os.Getenv("ynab_secret")
envVars["ynab_budget_id"] = os.Getenv("ynab_budget_id")
// Validate that all required environment variables are set
for key, value := range envVars {
if value == "" {
log.Fatalf("shell environment variable %s is not set", key)
}
}
// Loop through all account providers and attempt to configure them
// if configuration fails, the provider will not be used
configuredProviders = make([]AccountProvider, 0)
for _, p := range allProviders {
err := p.Configure()
if err != nil {
log.Printf("skipping provider '%s': %v", p.Name(), err)
continue
}
configuredProviders = append(configuredProviders, p)
log.Printf("enabled provider '%s'", p.Name())
}
// ynab client is static and has no persistent data so is initialized here and not in main program loop
var err error
ynabClient, err = ynab.NewClient(envVars["ynab_budget_id"], envVars["ynab_secret"])
if err != nil {
log.Fatalf("failed to create ynab client: %v", err)
}
// Web Templates
// Parse all template files at startup
t, err = template.ParseGlob("./templates/*")
if err != nil {
log.Fatalf("couldn't parse HTML templates: %v", err)
}
refreshRunning = &sync.Mutex{}
}
func main() {
go webServer()
for { // Main program loop
refreshData()
log.Print("Sleeping for 6 hours...")
time.Sleep(time.Hour * 6)
}
}
func refreshData() {
refreshRunning.Lock()
defer refreshRunning.Unlock()
// Only allow a refresh at most once every 5 minutes
if time.Now().Before(lastRefresh.Add(time.Minute * 5)) {
log.Printf("refresh rate limited")
return
}
lastRefresh = time.Now()
// Loop through each configured account provider and attempt to get the account balances, and update YNAB
for _, p := range configuredProviders {
balances, accountIDs, err := p.GetBalances()
if err != nil {
log.Printf("failed to get balances with provider '%s': %v", p.Name(), err)
continue
}
if len(balances) != len(accountIDs) {
log.Printf("'%s' provider data validation error: mismatched balance and accountID slice lengths - expected the same: balances length = %d, accountIDs length = %d", p.Name(), len(balances), len(accountIDs))
continue
}
for i := range balances {
err = ynabClient.SetAccountBalance(accountIDs[i], balances[i])
if err != nil {
log.Printf("failed to update ynab account '%s' balance: %v", accountIDs[i], err)
}
}
}
}