This commit is contained in:
@@ -181,11 +181,13 @@ func (api *API) generateTemplates() *multitemplate.Renderer {
|
||||
templates := make(map[string]*template.Template)
|
||||
render := multitemplate.NewRenderer()
|
||||
helperFuncs := template.FuncMap{
|
||||
"GetSVGGraphData": getSVGGraphData,
|
||||
"GetUTCOffsets": getUTCOffsets,
|
||||
"NiceSeconds": niceSeconds,
|
||||
"dict": dict,
|
||||
"fields": fields,
|
||||
"getSVGGraphData": getSVGGraphData,
|
||||
"getUTCOffsets": getUTCOffsets,
|
||||
"hasPrefix": strings.HasPrefix,
|
||||
"niceNumbers": niceNumbers,
|
||||
"niceSeconds": niceSeconds,
|
||||
}
|
||||
|
||||
// Load Base
|
||||
|
||||
@@ -14,6 +14,8 @@ import (
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -269,21 +271,46 @@ func (api *API) appGetHome(c *gin.Context) {
|
||||
templateVars, auth := api.getBaseTemplateVars("home", c)
|
||||
|
||||
start := time.Now()
|
||||
graphData, _ := api.DB.Queries.GetDailyReadStats(api.DB.Ctx, auth.UserName)
|
||||
log.Debug("[appGetHome] GetDailyReadStats Performance: ", time.Since(start))
|
||||
graphData, err := api.DB.Queries.GetDailyReadStats(api.DB.Ctx, auth.UserName)
|
||||
if err != nil {
|
||||
log.Error("[appGetHome] GetDailyReadStats DB Error: ", err)
|
||||
errorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetDailyReadStats DB Error: %v", err))
|
||||
return
|
||||
}
|
||||
log.Debug("[appGetHome] GetDailyReadStats DB Performance: ", time.Since(start))
|
||||
|
||||
start = time.Now()
|
||||
databaseInfo, _ := api.DB.Queries.GetDatabaseInfo(api.DB.Ctx, auth.UserName)
|
||||
log.Debug("[appGetHome] GetDatabaseInfo Performance: ", time.Since(start))
|
||||
databaseInfo, err := api.DB.Queries.GetDatabaseInfo(api.DB.Ctx, auth.UserName)
|
||||
if err != nil {
|
||||
log.Error("[appGetHome] GetDatabaseInfo DB Error: ", err)
|
||||
errorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetDatabaseInfo DB Error: %v", err))
|
||||
return
|
||||
}
|
||||
log.Debug("[appGetHome] GetDatabaseInfo DB Performance: ", time.Since(start))
|
||||
|
||||
streaks, _ := api.DB.Queries.GetUserStreaks(api.DB.Ctx, auth.UserName)
|
||||
WPMLeaderboard, _ := api.DB.Queries.GetWPMLeaderboard(api.DB.Ctx)
|
||||
start = time.Now()
|
||||
streaks, err := api.DB.Queries.GetUserStreaks(api.DB.Ctx, auth.UserName)
|
||||
if err != nil {
|
||||
log.Error("[appGetHome] GetUserStreaks DB Error: ", err)
|
||||
errorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetUserStreaks DB Error: %v", err))
|
||||
return
|
||||
}
|
||||
log.Debug("[appGetHome] GetUserStreaks DB Performance: ", time.Since(start))
|
||||
|
||||
start = time.Now()
|
||||
userStatistics, err := api.DB.Queries.GetUserStatistics(api.DB.Ctx)
|
||||
if err != nil {
|
||||
log.Error("[appGetHome] GetUserStatistics DB Error: ", err)
|
||||
errorPage(c, http.StatusInternalServerError, fmt.Sprintf("GetUserStatistics DB Error: %v", err))
|
||||
return
|
||||
}
|
||||
log.Debug("[appGetHome] GetUserStatistics DB Performance: ", time.Since(start))
|
||||
|
||||
templateVars["Data"] = gin.H{
|
||||
"Streaks": streaks,
|
||||
"GraphData": graphData,
|
||||
"DatabaseInfo": databaseInfo,
|
||||
"WPMLeaderboard": WPMLeaderboard,
|
||||
"UserStatistics": arrangeUserStatistics(userStatistics),
|
||||
}
|
||||
|
||||
c.HTML(http.StatusOK, "page/home", templateVars)
|
||||
@@ -385,7 +412,8 @@ func (api *API) appPerformAdminAction(c *gin.Context) {
|
||||
// 1. Consume backup ZIP
|
||||
// 2. Move existing to "backup" folder (db, wal, shm, covers, documents)
|
||||
// 3. Extract backup zip
|
||||
// 4. Restart server?
|
||||
// 4. Invalidate cookies (see in auth.go logout)
|
||||
// 5. Restart server?
|
||||
case adminBackup:
|
||||
// Get File Paths
|
||||
fileName := fmt.Sprintf("%s.db", api.Config.DBName)
|
||||
@@ -1244,3 +1272,80 @@ func errorPage(c *gin.Context, errorCode int, errorMessage string) {
|
||||
"Message": errorMessage,
|
||||
})
|
||||
}
|
||||
|
||||
func arrangeUserStatistics(userStatistics []database.GetUserStatisticsRow) gin.H {
|
||||
// Item Sorter
|
||||
sortItem := func(userStatistics []database.GetUserStatisticsRow, key string, less func(i int, j int) bool) []map[string]interface{} {
|
||||
sortedData := append([]database.GetUserStatisticsRow(nil), userStatistics...)
|
||||
sort.SliceStable(sortedData, less)
|
||||
|
||||
newData := make([]map[string]interface{}, 0)
|
||||
for _, item := range sortedData {
|
||||
v := reflect.Indirect(reflect.ValueOf(item))
|
||||
|
||||
var value string
|
||||
if strings.Contains(key, "Wpm") {
|
||||
rawVal := v.FieldByName(key).Float()
|
||||
value = fmt.Sprintf("%.2f WPM", rawVal)
|
||||
} else if strings.Contains(key, "Seconds") {
|
||||
rawVal := v.FieldByName(key).Int()
|
||||
value = niceSeconds(rawVal)
|
||||
} else if strings.Contains(key, "Words") {
|
||||
rawVal := v.FieldByName(key).Int()
|
||||
value = niceNumbers(rawVal)
|
||||
}
|
||||
|
||||
newData = append(newData, map[string]interface{}{
|
||||
"UserID": item.UserID,
|
||||
"Value": value,
|
||||
})
|
||||
}
|
||||
|
||||
return newData
|
||||
}
|
||||
|
||||
return gin.H{
|
||||
"WPM": gin.H{
|
||||
"All": sortItem(userStatistics, "TotalWpm", func(i, j int) bool {
|
||||
return userStatistics[i].TotalWpm > userStatistics[j].TotalWpm
|
||||
}),
|
||||
"Year": sortItem(userStatistics, "YearlyWpm", func(i, j int) bool {
|
||||
return userStatistics[i].YearlyWpm > userStatistics[j].YearlyWpm
|
||||
}),
|
||||
"Month": sortItem(userStatistics, "MonthlyWpm", func(i, j int) bool {
|
||||
return userStatistics[i].MonthlyWpm > userStatistics[j].MonthlyWpm
|
||||
}),
|
||||
"Week": sortItem(userStatistics, "WeeklyWpm", func(i, j int) bool {
|
||||
return userStatistics[i].WeeklyWpm > userStatistics[j].WeeklyWpm
|
||||
}),
|
||||
},
|
||||
"Duration": gin.H{
|
||||
"All": sortItem(userStatistics, "TotalSeconds", func(i, j int) bool {
|
||||
return userStatistics[i].TotalSeconds > userStatistics[j].TotalSeconds
|
||||
}),
|
||||
"Year": sortItem(userStatistics, "YearlySeconds", func(i, j int) bool {
|
||||
return userStatistics[i].YearlySeconds > userStatistics[j].YearlySeconds
|
||||
}),
|
||||
"Month": sortItem(userStatistics, "MonthlySeconds", func(i, j int) bool {
|
||||
return userStatistics[i].MonthlySeconds > userStatistics[j].MonthlySeconds
|
||||
}),
|
||||
"Week": sortItem(userStatistics, "WeeklySeconds", func(i, j int) bool {
|
||||
return userStatistics[i].WeeklySeconds > userStatistics[j].WeeklySeconds
|
||||
}),
|
||||
},
|
||||
"Words": gin.H{
|
||||
"All": sortItem(userStatistics, "TotalWordsRead", func(i, j int) bool {
|
||||
return userStatistics[i].TotalWordsRead > userStatistics[j].TotalWordsRead
|
||||
}),
|
||||
"Year": sortItem(userStatistics, "YearlyWordsRead", func(i, j int) bool {
|
||||
return userStatistics[i].YearlyWordsRead > userStatistics[j].YearlyWordsRead
|
||||
}),
|
||||
"Month": sortItem(userStatistics, "MonthlyWordsRead", func(i, j int) bool {
|
||||
return userStatistics[i].MonthlyWordsRead > userStatistics[j].MonthlyWordsRead
|
||||
}),
|
||||
"Week": sortItem(userStatistics, "WeeklyWordsRead", func(i, j int) bool {
|
||||
return userStatistics[i].WeeklyWordsRead > userStatistics[j].WeeklyWordsRead
|
||||
}),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
33
api/utils.go
33
api/utils.go
@@ -4,6 +4,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"reflect"
|
||||
|
||||
"reichard.io/antholume/database"
|
||||
"reichard.io/antholume/graph"
|
||||
@@ -87,6 +88,24 @@ func niceSeconds(input int64) (result string) {
|
||||
return
|
||||
}
|
||||
|
||||
func niceNumbers(input int64) string {
|
||||
if input == 0 {
|
||||
return "0"
|
||||
}
|
||||
|
||||
abbreviations := []string{"", "k", "M", "B", "T"}
|
||||
abbrevIndex := int(math.Log10(float64(input)) / 3)
|
||||
scaledNumber := float64(input) / math.Pow(10, float64(abbrevIndex*3))
|
||||
|
||||
if scaledNumber >= 100 {
|
||||
return fmt.Sprintf("%.0f%s", scaledNumber, abbreviations[abbrevIndex])
|
||||
} else if scaledNumber >= 10 {
|
||||
return fmt.Sprintf("%.1f%s", scaledNumber, abbreviations[abbrevIndex])
|
||||
} else {
|
||||
return fmt.Sprintf("%.2f%s", scaledNumber, abbreviations[abbrevIndex])
|
||||
}
|
||||
}
|
||||
|
||||
// Convert Database Array -> Int64 Array
|
||||
func getSVGGraphData(inputData []database.GetDailyReadStatsRow, svgWidth int, svgHeight int) graph.SVGGraphData {
|
||||
var intData []int64
|
||||
@@ -111,3 +130,17 @@ func dict(values ...interface{}) (map[string]interface{}, error) {
|
||||
}
|
||||
return dict, nil
|
||||
}
|
||||
|
||||
func fields(value interface{}) (map[string]interface{}, error) {
|
||||
v := reflect.Indirect(reflect.ValueOf(value))
|
||||
if v.Kind() != reflect.Struct {
|
||||
return nil, fmt.Errorf("%T is not a struct", value)
|
||||
}
|
||||
m := make(map[string]interface{})
|
||||
t := v.Type()
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
sv := t.Field(i)
|
||||
m[sv.Name] = v.Field(i).Interface()
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user