From 401878325ee458c2ea8f186b3243a92cc1cb07c4 Mon Sep 17 00:00:00 2001 From: Jo Vandeginste Date: Fri, 23 Feb 2024 13:16:19 +0100 Subject: [PATCH] Add profile editing form Signed-off-by: Jo Vandeginste --- assets/output.css | 17 --------- main.css | 7 ---- pkg/app/app.go | 40 ++++++++++++++++---- pkg/app/data.go | 1 + pkg/app/i18n.go | 40 ++++++++++++++++++++ pkg/app/routes.go | 12 +++--- pkg/app/self_handlers.go | 45 +++++++++++++++++++++++ pkg/app/tempates.go | 2 + pkg/app/users_handlers.go | 28 +------------- pkg/database/user.go | 12 ++++-- pkg/database/workouts.go | 2 + views/partials/user_profile_language.html | 16 ++++++++ views/partials/user_profile_theme.html | 8 ++++ views/user/user_profile.html | 27 ++++++++++++++ 14 files changed, 188 insertions(+), 69 deletions(-) create mode 100644 pkg/app/i18n.go create mode 100644 pkg/app/self_handlers.go create mode 100644 views/partials/user_profile_language.html create mode 100644 views/partials/user_profile_theme.html diff --git a/assets/output.css b/assets/output.css index 645b5074..38495cc6 100644 --- a/assets/output.css +++ b/assets/output.css @@ -565,13 +565,6 @@ form { label { text-align: right; } - label { - font-weight: 700; - } - label::after { - --tw-content: ':'; - content: var(--tw-content); - } } } @@ -860,19 +853,9 @@ table { padding-left: 0.5rem; padding-right: 0.5rem; } - td { - font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; - } tr { margin-bottom: 0.5rem; } - tbody th { - font-weight: 700; - } - tbody th::after { - --tw-content: ':'; - content: var(--tw-content); - } } .workout-tile-info { diff --git a/main.css b/main.css index 904a6567..b2fdbbf6 100644 --- a/main.css +++ b/main.css @@ -39,7 +39,6 @@ @apply mb-2 gap-4; label { @apply text-right py-1 block; - @apply font-bold after:content-[':']; } } } @@ -138,15 +137,9 @@ td { @apply px-2; } - td { - @apply font-mono; - } tr { @apply mb-2; } - tbody th { - @apply font-bold after:content-[':']; - } } .workout-tile-info { diff --git a/pkg/app/app.go b/pkg/app/app.go index 20454502..2439b4b2 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -18,12 +18,6 @@ import ( "github.com/vorlif/spreak" "github.com/vorlif/spreak/humanize" - "github.com/vorlif/spreak/humanize/locale/nl" -) - -const ( - DefaultTheme = "browser" - DefaultLanguage = "browser" ) type Version struct { @@ -67,7 +61,7 @@ func (a *App) ConfigureLocalizer() error { // Set the path from which the translations should be loaded spreak.WithDomainFs(spreak.NoDomain, a.Translations), // Specify the languages you want to load - spreak.WithLanguage(language.Dutch), + spreak.WithLanguage(translations()...), ) if err != nil { return err @@ -76,12 +70,19 @@ func (a *App) ConfigureLocalizer() error { a.translator = bundle a.humanizer = humanize.MustNew( - humanize.WithLocale(nl.New()), + humanize.WithLocale(humanLocales()...), ) return nil } +func (a *App) Serve() error { + go a.BackgroundWorker() + + a.logger.Info("Starting web server on " + a.Config.Bind) + return a.echo.Start(a.Config.Bind) +} + func (a *App) Configure() error { if err := a.ReadConfiguration(); err != nil { return err @@ -169,3 +170,26 @@ func (a *App) createAdminUser() error { return u.Create(a.db) } + +func (a *App) BackgroundWorker() { + l := a.logger.With("module", "worker") + + for { + l.Info("Worker started...") + + var w []database.Workout + + if err := a.db.Where(&database.Workout{Dirty: true}).Limit(10).Find(&w).Error; err != nil { + l.Error("Worker error: " + err.Error()) + } + + for _, v := range w { + if err := v.UpdateData(a.db); err != nil { + l.Error("Worker error: " + err.Error()) + } + } + + l.Info("Worker finished...") + time.Sleep(time.Minute) + } +} diff --git a/pkg/app/data.go b/pkg/app/data.go index 610ea0ac..16e9f0ea 100644 --- a/pkg/app/data.go +++ b/pkg/app/data.go @@ -67,6 +67,7 @@ func (a *App) addUserInfo(data map[string]interface{}, c echo.Context) { data["currentUser"] = u data["userProfileLanguage"] = u.Profile.Language + data["userProfileTheme"] = u.Profile.Theme } func (a *App) addWorkouts(u *database.User, data map[string]interface{}) error { diff --git a/pkg/app/i18n.go b/pkg/app/i18n.go new file mode 100644 index 00000000..91a783b5 --- /dev/null +++ b/pkg/app/i18n.go @@ -0,0 +1,40 @@ +package app + +import ( + "github.com/vorlif/spreak/humanize" + "github.com/vorlif/spreak/humanize/locale/nl" + "golang.org/x/text/language" +) + +var ( + DefaultLanguage = "browser" + + DefaultTheme = Theme{Name: "System default", Code: "browser", Icon: "🌐"} + DarkTheme = Theme{Name: "Dark theme", Code: "dark", Icon: "🌑"} +) + +func translations() []interface{} { + return []interface{}{ + language.English, + language.Dutch, + } +} + +func humanLocales() []*humanize.LocaleData { + return []*humanize.LocaleData{ + nl.New(), + } +} + +func themes() []Theme { + return []Theme{ + DefaultTheme, + DarkTheme, + } +} + +type Theme struct { + Code string + Icon string + Name string +} diff --git a/pkg/app/routes.go b/pkg/app/routes.go index 938fc37b..21e37644 100644 --- a/pkg/app/routes.go +++ b/pkg/app/routes.go @@ -120,8 +120,11 @@ func (a *App) secureRoutes(e *echo.Group) *echo.Group { secureGroup.GET("/", a.dashboardHandler).Name = "dashboard" secureGroup.GET("/statistics", a.statisticsHandler).Name = "statistics" - secureGroup.GET("/user/profile", a.userProfileHandler).Name = "user-profile" - secureGroup.POST("/user/refresh", a.userRefreshHandler).Name = "user-refresh" + + selfGroup := secureGroup.Group("/user") + selfGroup.GET("/profile", a.userProfileHandler).Name = "user-profile" + selfGroup.POST("/profile", a.userProfileUpdateHandler).Name = "user-profile-update" + selfGroup.POST("/refresh", a.userRefreshHandler).Name = "user-refresh" usersGroup := secureGroup.Group("/users") usersGroup.GET("/:id", a.userShowHandler).Name = "user-show" @@ -155,8 +158,3 @@ func (a *App) adminRoutes(e *echo.Group) *echo.Group { return adminGroup } - -func (a *App) Serve() error { - a.logger.Info("Starting web server on " + a.Config.Bind) - return a.echo.Start(a.Config.Bind) -} diff --git a/pkg/app/self_handlers.go b/pkg/app/self_handlers.go new file mode 100644 index 00000000..77ccf255 --- /dev/null +++ b/pkg/app/self_handlers.go @@ -0,0 +1,45 @@ +package app + +import ( + "net/http" + + "github.com/jovandeginste/workout-tracker/pkg/database" + "github.com/labstack/echo/v4" +) + +func (a *App) userProfileHandler(c echo.Context) error { + data := a.defaultData(c) + return c.Render(http.StatusOK, "user_profile.html", data) +} + +func (a *App) userProfileUpdateHandler(c echo.Context) error { + u := a.getCurrentUser(c) + p := &database.Profile{} + + if err := c.Bind(p); err != nil { + return a.redirectWithError(c, a.echo.Reverse("user-profile"), err) + } + + u.Profile.Language = p.Language + u.Profile.Theme = p.Theme + + if err := u.Profile.Save(a.db); err != nil { + return a.redirectWithError(c, a.echo.Reverse("user-profile"), err) + } + + a.setNotice(c, "Profile updated") + + return c.Redirect(http.StatusFound, a.echo.Reverse("user-profile")) +} + +func (a *App) userRefreshHandler(c echo.Context) error { + u := a.getCurrentUser(c) + + if err := u.MarkWorkoutsDirty(a.db); err != nil { + return a.redirectWithError(c, a.echo.Reverse("user-profile"), err) + } + + a.setNotice(c, "All workouts will be refreshed in the coming minutes.") + + return c.Redirect(http.StatusFound, a.echo.Reverse("user-profile")) +} diff --git a/pkg/app/tempates.go b/pkg/app/tempates.go index 3390e389..a7118b81 100644 --- a/pkg/app/tempates.go +++ b/pkg/app/tempates.go @@ -84,6 +84,8 @@ func (a *App) viewTemplateFunctions() template.FuncMap { "BoolToCheckbox": templatehelpers.BoolToCheckbox, "BuildDecoratedAttribute": templatehelpers.BuildDecoratedAttribute, "ToLanguageInformation": templatehelpers.ToLanguageInformation, + "supportedLanguaged": a.translator.SupportedLanguages, + "supportedThemes": themes, "RelativeDate": h.NaturalTime, diff --git a/pkg/app/users_handlers.go b/pkg/app/users_handlers.go index 96ce8af7..69088023 100644 --- a/pkg/app/users_handlers.go +++ b/pkg/app/users_handlers.go @@ -68,7 +68,7 @@ func (a *App) userRegisterHandler(c echo.Context) error { return a.redirectWithError(c, a.echo.Reverse("user-login"), fmt.Errorf("%w: %s", ErrInternalError, err)) } - u.Profile.Theme = DefaultLanguage + u.Profile.Theme = DefaultTheme.Code u.Profile.Language = DefaultLanguage if err := u.Create(a.db); err != nil { @@ -80,32 +80,6 @@ func (a *App) userRegisterHandler(c echo.Context) error { return c.Redirect(http.StatusFound, a.echo.Reverse("user-login")) } -func (a *App) userProfileHandler(c echo.Context) error { - data := a.defaultData(c) - return c.Render(http.StatusOK, "user_profile.html", data) -} - -func (a *App) userRefreshHandler(c echo.Context) error { - u := a.getCurrentUser(c) - - workouts, err := u.GetWorkouts(a.db) - if err != nil { - return a.redirectWithError(c, a.echo.Reverse("user-signout"), err) - } - - for _, w := range workouts { - a.logger.Debug("Refreshing workout: " + w.Name) - - if err := w.UpdateData(a.db); err != nil { - return a.redirectWithError(c, a.echo.Reverse("user-signout"), err) - } - } - - a.setNotice(c, "All workouts have been refreshed.") - - return c.Redirect(http.StatusFound, a.echo.Reverse("user-profile")) -} - func (a *App) userShowHandler(c echo.Context) error { data := a.defaultData(c) diff --git a/pkg/database/user.go b/pkg/database/user.go index 203909fe..b1714dbd 100644 --- a/pkg/database/user.go +++ b/pkg/database/user.go @@ -77,11 +77,13 @@ func GetUser(db *gorm.DB, username string) (*User, error) { type Profile struct { gorm.Model UserID int - Theme ThemePreference - Language string + Theme string `form:"theme"` + Language string `form:"language"` } -type ThemePreference string +func (p *Profile) Save(db *gorm.DB) error { + return db.Save(p).Error +} func (u *User) IsActive() bool { if u == nil { @@ -174,6 +176,10 @@ func (u *User) GetWorkout(db *gorm.DB, id int) (*Workout, error) { return w, nil } +func (u *User) MarkWorkoutsDirty(db *gorm.DB) error { + return db.Model(&Workout{}).Where(&Workout{UserID: u.ID}).Update("dirty", true).Error +} + func (u *User) GetWorkouts(db *gorm.DB) ([]*Workout, error) { var w []*Workout diff --git a/pkg/database/workouts.go b/pkg/database/workouts.go index 04d2b04b..8270277c 100644 --- a/pkg/database/workouts.go +++ b/pkg/database/workouts.go @@ -13,6 +13,7 @@ type Workout struct { Name string `gorm:"nut null"` Date *time.Time `gorm:"not null"` UserID uint `gorm:"not null;index"` + Dirty bool User *User Notes string Type string @@ -94,6 +95,7 @@ func (w *Workout) UpdateData(db *gorm.DB) error { } w.Data = gpxAsMapData(gpxContent) + w.Dirty = false return db.Save(w).Error } diff --git a/views/partials/user_profile_language.html b/views/partials/user_profile_language.html new file mode 100644 index 00000000..0e6b92b1 --- /dev/null +++ b/views/partials/user_profile_language.html @@ -0,0 +1,16 @@ +{{ define "user_profile_language" }} {{ $userLanguage := . }} + +{{ end }} diff --git a/views/partials/user_profile_theme.html b/views/partials/user_profile_theme.html new file mode 100644 index 00000000..847c85df --- /dev/null +++ b/views/partials/user_profile_theme.html @@ -0,0 +1,8 @@ +{{ define "user_profile_theme" }}{{ $userTheme := . }} + +{{ end }} diff --git a/views/user/user_profile.html b/views/user/user_profile.html index af8f789b..224a3056 100644 --- a/views/user/user_profile.html +++ b/views/user/user_profile.html @@ -8,6 +8,33 @@

Your profile

+
+ + + + + + + + + + + + + + + +
+ + {{ template "user_profile_theme" .userProfileTheme }}
+ + + {{ template "user_profile_language" .userProfileLanguage }} +
+ +
+
+