Skip to content

Commit

Permalink
Merge pull request #83 from jovandeginste/add-per-user-units
Browse files Browse the repository at this point in the history
Add per-user units to the user's profile
  • Loading branch information
jovandeginste authored Apr 12, 2024
2 parents e2391da + be1fb48 commit 1162258
Show file tree
Hide file tree
Showing 21 changed files with 314 additions and 73 deletions.
8 changes: 8 additions & 0 deletions assets/output.css
Original file line number Diff line number Diff line change
Expand Up @@ -2624,6 +2624,14 @@ table {
content: "\f018";
}

.icon-ruler::before {
content: "\f545";
}

.icon-ruler::after {
content: "\f545";
}

.icon-sailboat::before {
content: "\e445";
}
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ require (
github.com/alexedwards/scs/v2 v2.4.0
github.com/cat-dealer/go-rand/v2 v2.0.0
github.com/codingsince1985/geo-golang v1.8.3
github.com/dustin/go-humanize v1.0.1
github.com/fsouza/slognil v0.4.0
github.com/glebarez/sqlite v1.10.0
github.com/golang-jwt/jwt/v5 v5.0.0
Expand Down Expand Up @@ -41,6 +40,7 @@ require (
github.com/aymerick/douceur v0.2.0 // indirect
github.com/client9/misspell v0.3.4 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/glebarez/go-sqlite v1.21.2 // indirect
github.com/go-sql-driver/mysql v1.7.0 // indirect
Expand Down
1 change: 1 addition & 0 deletions pkg/app/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ func (a *App) secureRoutes(e *echo.Group) *echo.Group {
selfGroup := secureGroup.Group("/user")
selfGroup.GET("/profile", a.userProfileHandler).Name = "user-profile"
selfGroup.POST("/profile", a.userProfileUpdateHandler).Name = "user-profile-update"
selfGroup.POST("/profile/preferred-units", a.userProfilePreferredUnitsUpdateHandler).Name = "user-profile-preferred-units-update"
selfGroup.POST("/refresh", a.userRefreshHandler).Name = "user-refresh"
selfGroup.POST("/reset-api-key", a.userProfileResetAPIKeyHandler).Name = "user-profile-reset-api-key"

Expand Down
23 changes: 23 additions & 0 deletions pkg/app/self_handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,29 @@ func (a *App) userProfileHandler(c echo.Context) error {
return c.Render(http.StatusOK, "user_profile.html", data)
}

func (a *App) userProfilePreferredUnitsUpdateHandler(c echo.Context) error {
u := a.getCurrentUser(c)
p := database.UserPreferredUnits{}

if err := c.Bind(&p); err != nil {
return a.redirectWithError(c, a.echo.Reverse("user-profile"), err)
}

u.Profile.PreferredUnits = p

if err := u.Profile.Save(a.db); err != nil {
return a.redirectWithError(c, a.echo.Reverse("user-profile"), err)
}

if err := a.setUser(c); err != nil {
return a.redirectWithError(c, a.echo.Reverse("user-profile"), err)
}

a.setNotice(c, "Preferred units updated")

return c.Redirect(http.StatusFound, a.echo.Reverse("user-profile"))
}

func (a *App) userProfileUpdateHandler(c echo.Context) error {
u := a.getCurrentUser(c)
p := &database.Profile{}
Expand Down
15 changes: 11 additions & 4 deletions pkg/app/templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ func (t *Template) Render(w io.Writer, name string, data interface{}, ctx echo.C
"CurrentUser": func() *database.User { return u },
"LocalTime": func(t time.Time) time.Time { return t.In(u.Timezone()) },
"LocalDate": func(t time.Time) string { return t.In(u.Timezone()).Format("2006-01-02 15:04") },

"HumanElevation": templatehelpers.HumanElevationFor(u.PreferredUnits().Elevation),
"HumanDistance": templatehelpers.HumanDistanceFor(u.PreferredUnits().Distance),
"HumanSpeed": templatehelpers.HumanSpeedFor(u.PreferredUnits().Speed),
"HumanTempo": templatehelpers.HumanTempoFor(u.PreferredUnits().Distance),
})

return r.ExecuteTemplate(w, name, data)
Expand Down Expand Up @@ -66,17 +71,19 @@ func (a *App) viewTemplateFunctions() template.FuncMap {

"NumericDuration": templatehelpers.NumericDuration,
"CountryCodeToFlag": templatehelpers.CountryCodeToFlag,
"ToKilometer": templatehelpers.ToKilometer,
"HumanDistance": templatehelpers.HumanDistance,
"HumanSpeed": templatehelpers.HumanSpeed,
"HumanTempo": templatehelpers.HumanTempo,
"HumanDuration": templatehelpers.HumanDuration,
"IconFor": templatehelpers.IconFor,
"BoolToHTML": templatehelpers.BoolToHTML,
"BoolToCheckbox": templatehelpers.BoolToCheckbox,
"BuildDecoratedAttribute": templatehelpers.BuildDecoratedAttribute,
"ToLanguageInformation": templatehelpers.ToLanguageInformation,
"Timezones": templatehelpers.Timezones,
"SelectIf": templatehelpers.SelectIf,

"HumanElevation": templatehelpers.HumanElevationM,
"HumanDistance": templatehelpers.HumanDistanceKM,
"HumanSpeed": templatehelpers.HumanSpeedKPH,
"HumanTempo": templatehelpers.HumanTempoKM,

"RelativeDate": h.NaturalTime,

Expand Down
9 changes: 9 additions & 0 deletions pkg/database/profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,18 @@ type Profile struct {
AutoImportDirectory string `form:"auto_import_directory"`
SocialsDisabled bool `form:"socials_disabled"`

PreferredUnits UserPreferredUnits `gorm:"serializer:json"`

User *User `gorm:"foreignKey:UserID" json:"-"`
}

type UserPreferredUnits struct {
Speed string `form:"speed" `
Distance string `form:"distance"`
Duration string `form:"duration"`
Elevation string `form:"elevation"`
}

func (p *Profile) Save(db *gorm.DB) error {
return db.Save(p).Error
}
Expand Down
8 changes: 8 additions & 0 deletions pkg/database/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@ type User struct {
db *gorm.DB
}

func (u *User) PreferredUnits() *UserPreferredUnits {
if u == nil {
return &UserPreferredUnits{}
}

return &u.Profile.PreferredUnits
}

func (u *User) Timezone() *time.Location {
if u == nil || u.Profile.Timezone == "" {
return time.UTC
Expand Down
4 changes: 3 additions & 1 deletion pkg/templatehelpers/icons.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ func categoryIcon(what string) string {
switch what {
case "source":
return iconDefaults + " icon-solid icon-bookmark"
case "units":
return iconDefaults + " icon-solid icon-ruler"
case "file":
return iconDefaults + " icon-solid icon-file"
case "distance":
Expand Down Expand Up @@ -95,7 +97,7 @@ func pageIcon(what string) string {
return iconDefaults + " icon-solid icon-chart-line"
case "statistics":
return iconDefaults + " icon-solid icon-chart-simple"
case "admin":
case "admin", "actions":
return iconDefaults + " icon-solid icon-gear"
case "user-profile":
return iconDefaults + " icon-solid icon-user-circle"
Expand Down
42 changes: 42 additions & 0 deletions pkg/templatehelpers/imperial.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package templatehelpers

import (
"fmt"
"math"
)

const (
milesPerKM = 0.621371192
feetPerMeter = 3.2808399
)

func HumanDistanceMile(d float64) string {
return fmt.Sprintf("%.2f mi", milesPerKM*d/1000)
}

func HumanSpeedMilePH(mps float64) string {
if mps == 0 {
return InvalidValue
}

kmph := 3.6 * mps

return fmt.Sprintf("%.2f mi/h", milesPerKM*kmph)
}

func HumanTempoMile(mps float64) string {
if mps == 0 {
return InvalidValue
}

mpm := 1000 / (60 * mps) / milesPerKM

wholeMinutes := math.Floor(mpm)
seconds := (mpm - wholeMinutes) * 60

return fmt.Sprintf("%d:%02d min/mi", int(wholeMinutes), int(seconds))
}

func HumanElevationFt(m float64) string {
return fmt.Sprintf("%.2f ft", feetPerMeter*m)
}
37 changes: 37 additions & 0 deletions pkg/templatehelpers/metric.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package templatehelpers

import (
"fmt"
"math"
)

func HumanDistanceKM(d float64) string {
return fmt.Sprintf("%.2f km", d/1000)
}

func HumanSpeedKPH(mps float64) string {
if mps == 0 {
return InvalidValue
}

kmph := 3.6 * mps

return fmt.Sprintf("%.2f km/h", kmph)
}

func HumanTempoKM(mps float64) string {
if mps == 0 {
return InvalidValue
}

mpk := 1000 / (60 * mps)

wholeMinutes := math.Floor(mpk)
seconds := (mpk - wholeMinutes) * 60

return fmt.Sprintf("%d:%02d min/km", int(wholeMinutes), int(seconds))
}

func HumanElevationM(m float64) string {
return fmt.Sprintf("%.2f m", m)
}
64 changes: 36 additions & 28 deletions pkg/templatehelpers/template_funcs.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
package templatehelpers

import (
"fmt"
"html/template"
"math"
"strings"
"time"

"github.com/dustin/go-humanize"
emojiflag "github.com/jayco/go-emoji-flag"
"golang.org/x/text/language"
"golang.org/x/text/language/display"
)

var englishTag = display.English.Languages()

const InvalidValue = "N/A"

func NumericDuration(d time.Duration) float64 {
return d.Seconds()
}
Expand All @@ -23,39 +22,40 @@ func CountryCodeToFlag(cc string) string {
return emojiflag.GetFlag(cc)
}

func ToKilometer(d float64) string {
return fmt.Sprintf("%.2f km", d/1000.0)
func HumanElevationFor(unit string) func(float64) string {
switch unit {
case "ft":
return HumanElevationFt
default:
return HumanElevationM
}
}

func HumanDistance(d float64) string {
value, prefix := humanize.ComputeSI(d)

return fmt.Sprintf("%.2f %sm", value, prefix)
func HumanDistanceFor(unit string) func(float64) string {
switch unit {
case "mi":
return HumanDistanceMile
default:
return HumanDistanceKM
}
}

func HumanSpeed(mps float64) string {
if mps == 0 {
return "N/A"
func HumanSpeedFor(unit string) func(float64) string {
switch unit {
case "mph":
return HumanSpeedMilePH
default:
return HumanSpeedKPH
}

mph := mps * 3600
value, prefix := humanize.ComputeSI(mph)

return fmt.Sprintf("%.2f %sm/h", value, prefix)
}

func HumanTempo(mps float64) string {
if mps == 0 {
return "N/A"
func HumanTempoFor(unit string) func(float64) string {
switch unit {
case "mi":
return HumanTempoMile
default:
return HumanTempoKM
}

mpk := 1000000 / (mps * 60)
value, prefix := humanize.ComputeSI(mpk)

wholeMinutes := math.Floor(value)
seconds := (value - wholeMinutes) * 60

return fmt.Sprintf("%d:%02d min/%sm", int(wholeMinutes), int(seconds), prefix)
}

func BoolToHTML(b bool) template.HTML {
Expand All @@ -66,6 +66,14 @@ func BoolToHTML(b bool) template.HTML {
return `<i class="text-rose-500 fas fa-times"></i>`
}

func SelectIf(v1, v2 string) template.HTML {
if v1 == v2 {
return "selected"
}

return ""
}

func BoolToCheckbox(b bool) template.HTML {
if b {
return "checked"
Expand Down
32 changes: 16 additions & 16 deletions pkg/templatehelpers/template_funcs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,29 +18,29 @@ func TestCountryCodeToFlag(t *testing.T) {
assert.Equal(t, "🇧🇪", CountryCodeToFlag("BE"))
}

func TestToKilometer(t *testing.T) {
assert.Equal(t, "0.00 km", ToKilometer(1.23))
assert.Equal(t, "1.23 km", ToKilometer(1234))
assert.Equal(t, "1234.57 km", ToKilometer(1234567))
func TestHumanDistanceKM(t *testing.T) {
assert.Equal(t, "0.00 km", HumanDistanceKM(1.23))
assert.Equal(t, "1.23 km", HumanDistanceKM(1234))
assert.Equal(t, "1234.57 km", HumanDistanceKM(1234567))
}

func TestHumanDistance(t *testing.T) {
assert.Equal(t, "1.23 m", HumanDistance(1.23))
assert.Equal(t, "1.23 km", ToKilometer(1234))
assert.Equal(t, "1.23 Mm", HumanDistance(1234567))
assert.Equal(t, "0.00 km", HumanDistanceKM(1.23))
assert.Equal(t, "1.23 km", HumanDistanceKM(1234))
assert.Equal(t, "1234.57 km", HumanDistanceKM(1234567))
}

func TestHumanSpeed(t *testing.T) {
assert.Equal(t, "4.43 km/h", HumanSpeed(1.23))
assert.Equal(t, "10.01 km/h", HumanSpeed(2.78))
assert.Equal(t, "17.96 km/h", HumanSpeed(4.99))
func TestHumanSpeedKPH(t *testing.T) {
assert.Equal(t, "4.43 km/h", HumanSpeedKPH(1.23))
assert.Equal(t, "10.01 km/h", HumanSpeedKPH(2.78))
assert.Equal(t, "17.96 km/h", HumanSpeedKPH(4.99))
}

func TestHumanTempo(t *testing.T) {
assert.Equal(t, "13:33 min/km", HumanTempo(1.23))
assert.Equal(t, "5:59 min/km", HumanTempo(2.78))
assert.Equal(t, "3:20 min/km", HumanTempo(4.99))
assert.Equal(t, "5:01 min/km", HumanTempo(3.32))
func TestHumanTempoKM(t *testing.T) {
assert.Equal(t, "13:33 min/km", HumanTempoKM(1.23))
assert.Equal(t, "5:59 min/km", HumanTempoKM(2.78))
assert.Equal(t, "3:20 min/km", HumanTempoKM(4.99))
assert.Equal(t, "5:01 min/km", HumanTempoKM(3.32))
}

func TestBoolToHTML(t *testing.T) {
Expand Down
Loading

0 comments on commit 1162258

Please sign in to comment.