all repos — well-binge @ c54c853f0992ba1c02d4023e77965d844a5091b5

Create positive, recurring habits.

add habits page
Marco Andronaco andronacomarco@gmail.com
Sun, 13 Oct 2024 02:25:38 +0200
commit

c54c853f0992ba1c02d4023e77965d844a5091b5

parent

f57a55bbacf624b72827d15dc12081258118746b

6 files changed, 139 insertions(+), 19 deletions(-)

jump to
M src/app/functions.gosrc/app/functions.go

@@ -14,9 +14,20 @@

"github.com/birabittoh/auth-boilerplate/src/email" ) +type HabitDisplay struct { + Class string + Name string + LastAck string + Disabled bool +} + const ( minUsernameLength = 3 maxUsernameLength = 10 + + classGood = "good" + classWarn = "warn" + classBad = "bad" ) var (

@@ -128,3 +139,35 @@ userID, ok := r.Context().Value(userContextKey).(uint)

db.Find(&user, userID) return user, ok } + +func formatDuration(d time.Duration) string { + // TODO: 48h1m13s --> 2.01 days + return d.String() +} + +func toHabitDisplay(habit Habit) HabitDisplay { + return HabitDisplay{ + Name: habit.Name, + LastAck: formatDuration(time.Since(habit.LastAck)), + Disabled: habit.Disabled, + Class: classGood, + } +} + +func getAllHabits(userID uint) (positives []HabitDisplay, negatives []HabitDisplay, err error) { + var habits []Habit + err = db.Model(&Habit{}).Where(&Habit{UserID: userID}).Find(&habits).Error + if err != nil { + return + } + + for _, habit := range habits { + habitDisplay := toHabitDisplay(habit) + if habit.Negative { + negatives = append(negatives, habitDisplay) + } else { + positives = append(positives, habitDisplay) + } + } + return +}
M src/app/handlers.gosrc/app/handlers.go

@@ -9,14 +9,26 @@ func getIndexHandler(w http.ResponseWriter, r *http.Request) {

xt.ExecuteTemplate(w, "index.tmpl", nil) } -func getProfileHandler(w http.ResponseWriter, r *http.Request) { +func getHabitsHandler(w http.ResponseWriter, r *http.Request) { user, ok := getLoggedUser(r) if !ok { http.Error(w, "Could not find user in context.", http.StatusInternalServerError) return } - xt.ExecuteTemplate(w, "profile.tmpl", map[string]interface{}{"User": user}) + positive, negative, err := getAllHabits(user.ID) + if err != nil { + http.Error(w, "Could not get user habits.", http.StatusInternalServerError) + return + } + + data := map[string]interface{}{ + "User": user, + "Positive": positive, + "Negative": negative, + } + + xt.ExecuteTemplate(w, "habits.tmpl", data) } func getRegisterHandler(w http.ResponseWriter, r *http.Request) {

@@ -29,7 +41,7 @@ if err != nil {

xt.ExecuteTemplate(w, "auth-login.tmpl", nil) return } - http.Redirect(w, r, "/profile", http.StatusFound) + http.Redirect(w, r, "/habits", http.StatusFound) } func getResetPasswordHandler(w http.ResponseWriter, r *http.Request) {
M src/app/init.gosrc/app/init.go

@@ -25,6 +25,28 @@ Username string `gorm:"unique"`

Email string `gorm:"unique"` PasswordHash string Salt string + + Habits []Habit +} + +type Habit struct { + gorm.Model + UserID uint + Name string + Days uint + LastAck time.Time + Negative bool + Disabled bool + + User User + Acks []Ack +} + +type Ack struct { + gorm.Model + HabitID uint + + Habit Habit } const (

@@ -84,7 +106,7 @@ if err != nil {

log.Fatal(err) } - db.AutoMigrate(&User{}) + db.AutoMigrate(&User{}, &Habit{}, &Ack{}) // Init template engine xt = extemplate.New()

@@ -95,18 +117,20 @@ }

// Handle routes http.HandleFunc("GET /", getIndexHandler) - http.HandleFunc("GET /profile", loginRequired(getProfileHandler)) + http.HandleFunc("GET /habits", loginRequired(getHabitsHandler)) + + // Auth http.HandleFunc("GET /register", getRegisterHandler) http.HandleFunc("GET /login", getLoginHandler) http.HandleFunc("GET /reset-password", getResetPasswordHandler) http.HandleFunc("GET /reset-password-confirm", getResetPasswordConfirmHandler) http.HandleFunc("GET /logout", logoutHandler) - http.HandleFunc("POST /login", postLoginHandler) http.HandleFunc("POST /register", postRegisterHandler) http.HandleFunc("POST /reset-password", postResetPasswordHandler) http.HandleFunc("POST /reset-password-confirm", postResetPasswordConfirmHandler) + // Static http.Handle("GET /static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static")))) // Start serving
A templates/habits.tmpl

@@ -0,0 +1,47 @@

+{{ extends "base.tmpl" }} + +{{define "title" -}}Habits{{end}} + +{{define "content" -}} + <h1>Welcome, <i>{{.User.Username}}</i>!</h1> + <a href="/logout">Logout</a><br /> + <h4>Positive habits</h4> + <table> + <thead> + <tr> + <td>Name</td> + <td>Last time</td> + <td>Enabled</td> + </tr> + </thead> + <tbody> + {{range .Positive}} + <tr class="{{.Class}}"> + <td>{{.Name}}</td> + <td>{{.LastAck}}</td> + <td><input type="checkbox" disabled{{ if not .Disabled }} checked{{end}} /></td> + </tr> + {{end}} + </tbody> + <tfoot></tfoot> + </table> + + <h4>Negative habits</h4> + <table> + <thead> + <tr> + <td>Name</td> + <td>Last time</td> + </tr> + </thead> + <tbody> + {{range .Negative}} + <tr class="{{.Class}}"> + <td>{{.Name}}</td> + <td>{{.LastAck}}</td> + </tr> + {{end}} + </tbody> + <tfoot></tfoot> + </table> +{{end}}
M templates/index.tmpltemplates/index.tmpl

@@ -1,9 +1,12 @@

{{ extends "base.tmpl" }} -{{define "title" -}}Auth boilerplate{{end}} +{{define "title" -}}Well-Binge{{end}} {{define "content" -}} - <h1>Auth boilerplate</i>!</h1> - <p>This page is accessible to anyone.</p> - <a href="/profile" class="button">Start</a> + <h1>Well-Binge</h1> + <h4>Create positive habits, get reminders, quit addictions.</h4> + <center> + <a href="/habits" class="button">Start now</a> + </center> + {{end}}
D templates/profile.tmpl

@@ -1,9 +0,0 @@

-{{ extends "base.tmpl" }} - -{{define "title" -}}User profile{{end}} - -{{define "content" -}} - <h1>Welcome, <i>{{.User.Username}}</i>!</h1> - <p>This page is only accessible by users who have logged in.</p> - <a href="/logout">Logout</a> -{{end}}