From b0f8d81254b0ca2c35ee9d31b5b21da318f92af5 Mon Sep 17 00:00:00 2001 From: Nils Jakobi Date: Sun, 8 Nov 2020 17:00:33 +0100 Subject: [PATCH] first draft version --- css/main.css | 123 +++++++++++++++++++++++ go.mod | 10 ++ home.go | 11 +++ main.go | 45 +++++++++ rewards.go | 210 ++++++++++++++++++++++++++++++++++++++++ templates/home.html | 6 ++ templates/includes.html | 68 +++++++++++++ templates/rewards.html | 25 +++++ 8 files changed, 498 insertions(+) create mode 100644 css/main.css create mode 100644 go.mod create mode 100644 home.go create mode 100644 main.go create mode 100644 rewards.go create mode 100644 templates/home.html create mode 100644 templates/includes.html create mode 100644 templates/rewards.html diff --git a/css/main.css b/css/main.css new file mode 100644 index 0000000..2270693 --- /dev/null +++ b/css/main.css @@ -0,0 +1,123 @@ +/* reset all margins and padding*/ +* { + margin: 0; + padding: 0; + font-family: 'Rosario', sans-serif !important; +} + +body { + background-color: #ffffff; +} + +h1 { + color: #17375e; + margin-left: 20px; + margin-top: 20px; + margin-bottom: 10px +} + +p { + color:#292929; + margin-left: 20px; + margin-bottom: 20px; +} + +nav ul { + list-style-type: none; + margin: 0; + padding: 0; + overflow: hidden; + background-color:#29755b; + position: fixed; + /* position: absolute; */ + /* top: 0; */ + width: 100%; + /* iOS padding fix, fixes notch problems and statusbar problems */ + /* top | right | bottom | left */ + padding: 0 env(safe-area-inset-right) 0 env(safe-area-inset-left); +} + +nav li { + float: left; + /* border-right: 10px solid #c90000; */ +} + + +nav li:last-child { + border-right: none; +} + +nav li a { + display: inline-block; + color: black; + text-align: center; + padding: 14px 16px; + text-decoration: none; +} + + +nav li a:hover { + color: white; +} + +.active { + background-color: #3caf87; + color: black; +} +/* a.active:hover{ + color: #4b5786; +} */ + +#container{width:900px;} +#left{float:left;width:215px;} +#right{float:right;width:215px;} +#center{margin:0 auto;width:215px;} + +.mainbody{ + /* iOS padding fix, fixes notch problems and statusbar problems */ + /* top | right | bottom | left */ + padding: 75px env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left); +} + +.submit{ + margin-top: 10px; +} + +.indent{ + margin-left: 30px; +} + +.err p{ + color: #960000; + font-weight: bold; +} + +.footer { + color:white; + background-color:#29755b; + /* margin-left: 20px; */ + position: fixed; + left: 0; + bottom: 0; + width: 100%; + text-align: center; + /* iOS padding fix, fixes notch problems and statusbar problems */ + /* top | right | bottom | left */ + padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left); +} + +.container .item { + width: 75%; + height: 60vh; +} + +.container { + margin-top:50px; + display: flex; + justify-content: center; + align-items: center; +} + +.item { + margin: auto; +} \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..8c5085c --- /dev/null +++ b/go.mod @@ -0,0 +1,10 @@ +module git.nils.zone/nils/goCake + +go 1.15 + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/kr/pretty v0.1.0 // indirect + github.com/stretchr/testify v1.6.1 + gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect +) diff --git a/home.go b/home.go new file mode 100644 index 0000000..2a1f475 --- /dev/null +++ b/home.go @@ -0,0 +1,11 @@ +package main + +import ( + "net/http" +) + +// Home is a handler for / renders the home.html +func Home(w http.ResponseWriter, req *http.Request) { + data := struct{ Title string }{Title: "Home"} + render(w, "home.html", data) +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..8d9cd28 --- /dev/null +++ b/main.go @@ -0,0 +1,45 @@ +package main + +import ( + "html/template" + "net/http" + "os" +) + +func main() { + // this code below makes a file server and serves everything as a file + // fs := http.FileServer(http.Dir("")) + // http.Handle("/", fs) + + // serve everything in the css folder, the img folder and mp3 folder as a file + http.Handle("/css/", http.StripPrefix("/css/", http.FileServer(http.Dir("css")))) + + // when navigating to /home it should serve the home page + http.HandleFunc("/", Home) + http.HandleFunc("/rewards", Upload) + http.ListenAndServe(getPort(), nil) +} + +// Detect $PORT and if present uses it for listen and serve else defaults to :8080 +// This is so that app can run on Heroku +func getPort() string { + p := os.Getenv("PORT") + if p != "" { + return ":" + p + } + return ":8080" +} + +func render(w http.ResponseWriter, tmpl string, data interface{}) { + //parse the template file held in the templates folder + t, err := template.ParseFiles("templates/"+tmpl, "templates/includes.html") + if err != nil { + panic(err) + } + + // render the website and pass data to the templating engine + err = t.Execute(w, data) + if err != nil { + panic(err) + } +} diff --git a/rewards.go b/rewards.go new file mode 100644 index 0000000..e2ced1f --- /dev/null +++ b/rewards.go @@ -0,0 +1,210 @@ +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) + } +} diff --git a/templates/home.html b/templates/home.html new file mode 100644 index 0000000..1359481 --- /dev/null +++ b/templates/home.html @@ -0,0 +1,6 @@ +{{template "header" .}} +
+

Willkommen auf einer kleinen Testseite

+

Hier ist ja doch noch ein wenig was Platz...

+
+{{template "footer" .}} diff --git a/templates/includes.html b/templates/includes.html new file mode 100644 index 0000000..6a06585 --- /dev/null +++ b/templates/includes.html @@ -0,0 +1,68 @@ +{{define "header"}} + + + + + + + +goCake - {{.Title}} + + + {{template "nav" .}} +{{end}} + +{{define "nav"}} + + + +{{end}} + +{{define "footer"}} + + + +{{end}} + +{{define "barGraph"}} +
+
+
+ +{{ end }} + +{{define "xAxis"}} +[{"data":[ {{ range $month, $amount := .Rewards}}"{{ $month }}",{{ end }}]}], +{{end}} + +{{define "series"}} +{"name":"{{.Currency}}","type":"bar","waveAnimation":false,"data":[ +{{ range $month, $amount := .Rewards}}{"value": "{{ $amount }}" },{{ end }} +]}, +{{end}} \ No newline at end of file diff --git a/templates/rewards.html b/templates/rewards.html new file mode 100644 index 0000000..a812fe9 --- /dev/null +++ b/templates/rewards.html @@ -0,0 +1,25 @@ +{{template "header" .}} +
+ {{if not .Success}} + +

If you upload your CSV file from Cake here, we will display some graphs here

+
+

+

+ + +
+

+
+ {{if .Uploaded}} + +
+

Please only upload .csv Files!

+
+ {{end}} + {{else}} + + {{template "barGraph" .}} + {{end}} +
+{{template "footer" .}} \ No newline at end of file