320 lines
8.2 KiB
Go
320 lines
8.2 KiB
Go
package main
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"log"
|
|
"math"
|
|
"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
|
|
}
|
|
|
|
// CakeRewards needs a comment
|
|
func CakeRewards(w http.ResponseWriter, r *http.Request) {
|
|
type data struct {
|
|
Title string
|
|
Uploaded bool
|
|
Success bool
|
|
Rewards rewards
|
|
CumulativeRewards map[time.Month]string
|
|
Colors []string
|
|
Currency string
|
|
ErrorText string
|
|
Lending bool
|
|
}
|
|
|
|
// set common attributes
|
|
d := data{
|
|
Title: "Pool by Cake 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
|
|
} else {
|
|
// set ErrorText
|
|
d.ErrorText = err.Error()
|
|
}
|
|
|
|
// prepare data for usage
|
|
var color string
|
|
var currency string
|
|
var precision int
|
|
var rewards rewards
|
|
if success == true {
|
|
currency = lines[1].Cryptocurrency
|
|
color, precision, d.Lending = getCurrencyOpts(currency)
|
|
rewards = monthlyRewardOverview(lines)
|
|
d.CumulativeRewards = getCumulative(rewards, precision)
|
|
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.Rewards.Airdrop = rewards.Airdrop
|
|
d.Rewards.Swapped = rewards.Swapped
|
|
d.Colors = getOtherColors(color, 8)
|
|
d.Currency = currency
|
|
}
|
|
d.Uploaded = true
|
|
|
|
// prettify.Print(rewards)
|
|
render(w, "rewards.html", d)
|
|
}
|
|
// create a new line instance
|
|
}
|
|
|
|
func getCumulative(r rewards, precision int) map[time.Month]string {
|
|
// create map to calculate cumulative rewards
|
|
res := make(map[time.Month]string)
|
|
|
|
for i := 1; i < 13; i++ {
|
|
month := time.Month(i)
|
|
// parse all fields
|
|
airdrop, err := strconv.ParseFloat(r.Airdrop[month], 64)
|
|
confectionery, err := strconv.ParseFloat(r.Confectionery[month], 64)
|
|
lapis, err := strconv.ParseFloat(r.Lapis[month], 64)
|
|
lapisDFI, err := strconv.ParseFloat(r.LapisDFI[month], 64)
|
|
referral, err := strconv.ParseFloat(r.Referral[month], 64)
|
|
staking, err := strconv.ParseFloat(r.Staking[month], 64)
|
|
swapped, err := strconv.ParseFloat(r.Swapped[month], 64)
|
|
|
|
// add all fields up and format result as string
|
|
res[month] = strconv.FormatFloat(airdrop+confectionery+lapis+lapisDFI+referral+staking+swapped, 'g', precision, 64)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
// return result
|
|
return res
|
|
}
|
|
|
|
func getOtherColors(color string, n int) []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
|
|
var c []string
|
|
for i := 1; i < int(math.Pow(2, float64(n))); i *= 2 {
|
|
c = append(c, colorful.LinearRgb(r/float64(i), g/float64(i), b/float64(i)).Hex())
|
|
}
|
|
|
|
return c
|
|
}
|
|
|
|
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)
|
|
// coinValue, err := GetCoinValue("bitcoin", "eur")
|
|
// if err == nil {
|
|
// 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),
|
|
Airdrop: fillSums(airdrop, precision),
|
|
Swapped: fillSums(swapped, 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
|
|
}
|