package main import ( "errors" "fmt" "io/ioutil" "log" "net/http" "runtime" "strconv" "strings" "time" "github.com/lucasb-eyer/go-colorful" ) type line struct { Date time.Time Operation string Cryptocurrency string Amount float64 TransactionID string WithdrawalAddress string Reference string } type rewards struct { Staking map[time.Month]string Confectionery map[time.Month]string LapisDFI map[time.Month]string Lapis map[time.Month]string Referral map[time.Month]string Airdrop map[time.Month]string Swapped map[time.Month]string } // Rewards needs a comment func Rewards(w http.ResponseWriter, r *http.Request) { type data struct { Title string Uploaded bool Success bool Rewards rewards CumulativeRewards map[time.Month]string Color string ColorAlt string ColorAltAlt string Currency string ErrorText string Lending bool } // set common attributes d := data{ Title: "Monthly Rewards", } // check the method used to access this site (GET/POST) switch r.Method { case http.MethodGet: // display upload site d.Uploaded = false d.Success = false render(w, "rewards.html", d) case http.MethodPost: // upload the file that was posted here var success bool = false var e string lines, err := uploadFile(w, r) if err == nil { success = true } else { e = err.Error() } // prepare data for usage var color string var currency string var rewards rewards if success == true { currency = lines[1].Cryptocurrency color, _, d.Lending = getCurrencyOpts(currency) rewards = monthlyRewardOverview(lines) } // prettify.Print(rewards) d.Uploaded = true d.Success = success d.Rewards.Staking = rewards.Staking d.Rewards.Confectionery = rewards.Confectionery d.Rewards.Lapis = rewards.Lapis d.Rewards.LapisDFI = rewards.LapisDFI d.Rewards.Referral = rewards.Referral d.Color = color d.ColorAlt = getOtherColors(color, 2) d.ColorAltAlt = getOtherColors(color, 4) d.Currency = currency d.ErrorText = e render(w, "rewards.html", d) } // create a new line instance } func getOtherColors(color string, factor float64) string { col, err := colorful.Hex(color) if err != nil { log.Fatal(err) } // get r,g,b from color r, g, b := col.LinearRgb() // return color reduced in "brightness" by factor of f return colorful.LinearRgb(r/factor, g/factor, b/factor).Hex() } func uploadFile(w http.ResponseWriter, r *http.Request) ([]line, error) { // Maximum upload of 10 MB files r.ParseMultipartForm(10 << 20) defer r.Body.Close() // Get handler for filename, size and headers file, handler, err := r.FormFile("csvFile") if err != nil { return nil, errors.New("please upload a .csv file") } defer file.Close() // check if we got a csv file switch handler.Header["Content-Type"][0] { case "text/csv", "application/vnd.ms-excel": // accept this gift and read content of file fileContents, err := ioutil.ReadAll(file) if err != nil { fmt.Println("file was uploaded but can't be read! 1", err) return nil, err } // read uploaded file lines, err := readUploadedFile(fileContents) if err != nil { return nil, fmt.Errorf("the .csv file could not be read, error was: %s", err) } return lines, err } return nil, errors.New("please only upload .csv files") } func getCurrencyOpts(currency string) (color string, precision int, lending bool) { // set default precision precision = 8 lending = false // set other options according to the coin switch currency { case "BTC": // Bitcoin color = "#F7931A" lending = true case "DASH": color = "#008de4" case "DFI": // DeFiChain color = "#fd0bb0" case "ETH": // Ethereum color = "#627eea" lending = true precision = 18 case "PIVX": color = "#5e4778" precision = 9 case "XZC": // Zcoin color = "#24b852" case "USDT": // Tether color = "#26a17b" lending = true precision = 6 } return color, precision, lending } func monthlyRewardOverview(lines []line) rewards { // create a map to hold all months' sums staking := make(map[time.Month]float64) confectionery := make(map[time.Month]float64) lapisDFI := make(map[time.Month]float64) lapis := make(map[time.Month]float64) referral := make(map[time.Month]float64) airdrop := make(map[time.Month]float64) swapped := make(map[time.Month]float64) // loop through all lines for i := range lines { // filter out operations switch lines[i].Operation { case "Staking reward": staking[lines[i].Date.Month()] += lines[i].Amount case "Confectionery Lapis DFI Bonus": confectionery[lines[i].Date.Month()] += lines[i].Amount case "Lapis DFI Bonus": lapisDFI[lines[i].Date.Month()] += lines[i].Amount case "Lapis reward": lapis[lines[i].Date.Month()] += lines[i].Amount case "Referral reward": referral[lines[i].Date.Month()] += lines[i].Amount case "Bonus/Airdrop": airdrop[lines[i].Date.Month()] += lines[i].Amount case "Swapped in": swapped[lines[i].Date.Month()] += lines[i].Amount } } // get precision for specific coin _, precision, _ := getCurrencyOpts(lines[0].Cryptocurrency) // fill empty data with zeroes r := rewards{ Staking: fillSums(staking, precision), Confectionery: fillSums(confectionery, precision), LapisDFI: fillSums(lapisDFI, precision), Lapis: fillSums(lapis, precision), Referral: fillSums(referral, precision), } return r } func fillSums(s map[time.Month]float64, precision int) map[time.Month]string { // maps are always referenced by pointers // create empty map sumsString := make(map[time.Month]string) // loop through month for i := 1; i < 13; i++ { // check what month are missing in array if _, ok := s[time.Month(i)]; !ok { s[time.Month(i)] = 0 } // format to strings sumsString[time.Month(i)] = strconv.FormatFloat(s[time.Month(i)], 'g', precision, 64) } return sumsString } func readUploadedFile(fileContents []byte) ([]line, error) { var lines []line var csvLines []string // newlines need to be handled differently on windows switch runtime.GOOS { case "windows": csvLines = strings.Split(string(fileContents), "\r\n") default: csvLines = strings.Split(string(fileContents), "\n") } // loop through all lines (except headers) for _, csvLine := range csvLines[1:] { if csvLine != "" { // split arguments by comma csvArgs := strings.Split(csvLine, ",") // fix stupid " in lines for i := range csvArgs { csvArgs[i] = strings.ReplaceAll(csvArgs[i], `"`, "") } // create output variable var l line // assign values from CSV file to output variable var err error l.Date, err = time.Parse(time.RFC3339, csvArgs[0]) if err != nil { return nil, err } l.Operation = csvArgs[1] l.Cryptocurrency = csvArgs[2] l.Amount, _ = strconv.ParseFloat(csvArgs[3], 64) l.TransactionID = csvArgs[4] l.WithdrawalAddress = csvArgs[5] l.Reference = csvArgs[6] lines = append(lines, l) } } return lines, nil }