first draft version
This commit is contained in:
commit
b0f8d81254
123
css/main.css
Normal file
123
css/main.css
Normal file
@ -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;
|
||||
}
|
10
go.mod
Normal file
10
go.mod
Normal file
@ -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
|
||||
)
|
11
home.go
Normal file
11
home.go
Normal file
@ -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)
|
||||
}
|
45
main.go
Normal file
45
main.go
Normal file
@ -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)
|
||||
}
|
||||
}
|
210
rewards.go
Normal file
210
rewards.go
Normal file
@ -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)
|
||||
}
|
||||
}
|
6
templates/home.html
Normal file
6
templates/home.html
Normal file
@ -0,0 +1,6 @@
|
||||
{{template "header" .}}
|
||||
<div class="mainbody">
|
||||
<p>Willkommen auf einer kleinen Testseite</p>
|
||||
<p>Hier ist ja doch noch ein wenig was Platz...</p>
|
||||
</div>
|
||||
{{template "footer" .}}
|
68
templates/includes.html
Normal file
68
templates/includes.html
Normal file
@ -0,0 +1,68 @@
|
||||
{{define "header"}}
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" type="text/css" href="../css/main.css">
|
||||
<!-- below line adds jQuery to the page -->
|
||||
<!-- <script type='text/javascript' src='https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js'></script> -->
|
||||
<script type='text/javascript' src="https://go-echarts.github.io/go-echarts-assets/assets/echarts.min.js"></script>
|
||||
<title>goCake - {{.Title}}</title>
|
||||
</head>
|
||||
<body>
|
||||
{{template "nav" .}}
|
||||
{{end}}
|
||||
|
||||
{{define "nav"}}
|
||||
<!-- iOS fix -->
|
||||
<meta name='viewport' content='initial-scale=1, viewport-fit=cover, width=device-width'>
|
||||
<nav>
|
||||
<ul>
|
||||
{{ if .Title}}
|
||||
<!-- set current tab active, according to title -->
|
||||
<li><a {{ if (eq .Title "Home") }}class="active"{{end}} href="/">Home</a></li>
|
||||
<!-- <li><a {{ if (eq .Title "Rewards") }}class="active"{{end}} href="rewards">Monthly Rewards</a></li> -->
|
||||
<!-- <li><a {{ if (eq .Title "Graphs") }}class="active"{{end}} href="graphs">Graphs</a></li> -->
|
||||
<li><a {{ if (eq .Title "Monthly Rewards") }}class="active"{{end}} href="rewards">Monthly Rewards</a></li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
</nav>
|
||||
{{end}}
|
||||
|
||||
{{define "footer"}}
|
||||
<div class="footer">♥ 2020 Nils Jakobi
|
||||
- <a href="https://git.nils.zone/nils/goCake">Source Code</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
||||
|
||||
{{define "barGraph"}}
|
||||
<div class="container">
|
||||
<div class="item" id="chart"></div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
"use strict";
|
||||
var rewardChart = echarts.init(document.getElementById('chart'), "white");
|
||||
var rewardOptions = {
|
||||
title: {"text":"Monthly Rewards","subtext":"{{.Currency}}"},
|
||||
tooltip: {"show":true},
|
||||
legend: {"show":false},
|
||||
dataZoom:[{"type":"inside"}],
|
||||
xAxis: {{ template "xAxis" . }}
|
||||
yAxis: [{}],
|
||||
series: [{{template "series" . }}],
|
||||
color: ["{{.Color}}"],
|
||||
};
|
||||
rewardChart.setOption(rewardOptions);
|
||||
</script>
|
||||
{{ 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}}
|
25
templates/rewards.html
Normal file
25
templates/rewards.html
Normal file
@ -0,0 +1,25 @@
|
||||
{{template "header" .}}
|
||||
<div class="mainbody">
|
||||
{{if not .Success}}
|
||||
<!-- not successful -->
|
||||
<p>If you upload your CSV file from Cake here, we will display some graphs here</p>
|
||||
<div class="indent">
|
||||
<p>
|
||||
<form enctype="multipart/form-data" action="/rewards" method="post">
|
||||
<input type="file" name="csvFile" />
|
||||
<input type="submit" value="upload" />
|
||||
</form>
|
||||
</p>
|
||||
</div>
|
||||
{{if .Uploaded}}
|
||||
<!-- Uploaded but not successful -->
|
||||
<div class="err">
|
||||
<p>Please only upload .csv Files!</p>
|
||||
</div>
|
||||
{{end}}
|
||||
{{else}}
|
||||
<!-- successful -->
|
||||
{{template "barGraph" .}}
|
||||
{{end}}
|
||||
</div>
|
||||
{{template "footer" .}}
|
Loading…
Reference in New Issue
Block a user