package main import ( "errors" "io/ioutil" "net/http" "strconv" "strings" "time" ) type line struct { Date time.Time Operation string Cryptocurrency string Amount float64 TransactionID string WithdrawalAddress string Reference string } // Upload needs a comment func Upload(w http.ResponseWriter, r *http.Request) { type data struct { Title string Uploaded bool Success bool Rewards map[time.Month]string CumulativeRewards map[time.Month]string Color string Currency string } // 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 lines, err := uploadFile(w, r) if err == nil { success = true } // prepare data for usage var color string var currency string var rewards map[time.Month]string if success == true { currency = lines[1].Cryptocurrency color, _ = getCurrencyOpts(currency) rewards = monthlyRewardOverview(lines) } // prettify.Print(rewards) d.Uploaded = true d.Success = success d.Rewards = rewards d.Color = color d.Currency = currency render(w, "rewards.html", d) } // create a new line instance } func uploadFile(w http.ResponseWriter, r *http.Request) ([]line, error) { // Maximum upload of 10 MB files r.ParseMultipartForm(10 << 20) // Get handler for filename, size and headers file, handler, err := r.FormFile("csvFile") if err != nil { return nil, err } defer file.Close() // check if we got a csv file if handler.Header["Content-Type"][0] == "text/csv" { // accept this gift and read content of file fileContents, err := ioutil.ReadAll(file) if err != nil { return nil, err } // read uploaded file lines := readUploadedFile(fileContents) return lines, err } return nil, errors.New("please only upload .csv files") } func getCurrencyOpts(currency string) (color string, precision int) { // set default precision precision = 8 // set other options according to the coin switch currency { case "BTC": // Bitcoin color = "#F7931A" case "DASH": color = "#008de4" case "DFI": // DeFiChain color = "#fd0bb0" case "ETH": // Ethereum color = "#627eea" precision = 18 case "PIVX": color = "#5e4778" precision = 9 case "XZC": // Zcoin color = "#24b852" case "USDT": // Tether color = "#26a17b" precision = 6 } return color, precision } func monthlyRewardOverview(lines []line) map[time.Month]string { // create a map to hold all months' sums sums := make(map[time.Month]float64) // loop through all lines for i := range lines { // filter out operations switch lines[i].Operation { case "Staking reward ", "Confectionery Lapis DFI Bonus ", "Lapis DFI Bonus ", "Lapis reward ", "Referral reward ": sums[lines[i].Date.Month()] += lines[i].Amount } } // get precision for specific coin _, precision := getCurrencyOpts(lines[0].Cryptocurrency) // fill empty data with zeroes sumsString := fillSums(sums, precision) return sumsString } 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 { var lines []line csvLines := strings.Split(string(fileContents), "\n") // loop through all lines (except headers) for _, csvLine := range csvLines[1:] { // 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 { panic(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 } func uploadHandler(w http.ResponseWriter, r *http.Request) { switch r.Method { case "GET": render(w, "upload", nil) case "POST": uploadFile(w, r) } }