all repos — well-binge @ c54c853f0992ba1c02d4023e77965d844a5091b5

Create positive, recurring habits.

src/app/functions.go (view raw)

  1package app
  2
  3import (
  4	"context"
  5	"errors"
  6	"fmt"
  7	"log"
  8	"net/http"
  9	"os"
 10	"regexp"
 11	"strings"
 12	"time"
 13
 14	"github.com/birabittoh/auth-boilerplate/src/email"
 15)
 16
 17type HabitDisplay struct {
 18	Class    string
 19	Name     string
 20	LastAck  string
 21	Disabled bool
 22}
 23
 24const (
 25	minUsernameLength = 3
 26	maxUsernameLength = 10
 27
 28	classGood = "good"
 29	classWarn = "warn"
 30	classBad  = "bad"
 31)
 32
 33var (
 34	validUsername = regexp.MustCompile(`(?i)^[a-z0-9._-]+$`)
 35	validEmail    = regexp.MustCompile(`^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$`)
 36)
 37
 38func getUserByName(username string, excluding uint) (user User, err error) {
 39	err = db.Model(&User{}).Where("upper(username) == upper(?) AND id != ?", username, excluding).First(&user).Error
 40	return
 41}
 42
 43func sanitizeUsername(username string) (string, error) {
 44	if !validUsername.MatchString(username) || len(username) < minUsernameLength || len(username) > maxUsernameLength {
 45		return "", errors.New("invalid username")
 46	}
 47
 48	return username, nil
 49}
 50
 51func sanitizeEmail(email string) (string, error) {
 52	email = strings.ToLower(email)
 53
 54	if !validEmail.MatchString(email) {
 55		return "", fmt.Errorf("invalid email")
 56	}
 57
 58	return email, nil
 59}
 60
 61func login(w http.ResponseWriter, userID uint, remember bool) {
 62	var duration time.Duration
 63	if remember {
 64		duration = durationWeek
 65	} else {
 66		duration = durationDay
 67	}
 68
 69	cookie, err := g.GenerateCookie(duration)
 70	if err != nil {
 71		http.Error(w, "Could not generate session cookie.", http.StatusInternalServerError)
 72	}
 73
 74	ks.Set("session:"+cookie.Value, userID, duration)
 75	http.SetCookie(w, cookie)
 76}
 77
 78func loadEmailConfig() *email.Client {
 79	address := os.Getenv("APP_SMTP_EMAIL")
 80	password := os.Getenv("APP_SMTP_PASSWORD")
 81	host := os.Getenv("APP_SMTP_HOST")
 82	port := os.Getenv("APP_SMTP_PORT")
 83
 84	if address == "" || password == "" || host == "" {
 85		log.Println("Missing email configuration.")
 86		return nil
 87	}
 88
 89	if port == "" {
 90		port = "587"
 91	}
 92
 93	return email.NewClient(address, password, host, port)
 94}
 95
 96func sendEmail(mail email.Email) error {
 97	if m == nil {
 98		return errors.New("email client is not initialized")
 99	}
100	return m.Send(mail)
101}
102
103func sendResetEmail(address, token string) {
104	resetURL := fmt.Sprintf("%s/reset-password-confirm?token=%s", baseUrl, token)
105	err := sendEmail(email.Email{
106		To:      []string{address},
107		Subject: "Reset password",
108		Body:    fmt.Sprintf("Use the following link to reset your password:\n%s", resetURL),
109	})
110	if err != nil {
111		log.Printf("Could not send reset email for %s. Link: %s", address, resetURL)
112	}
113}
114
115func readSessionCookie(r *http.Request) (userID *uint, err error) {
116	cookie, err := r.Cookie("session_token")
117	if err != nil {
118		return
119	}
120	return ks.Get("session:" + cookie.Value)
121}
122
123// Middleware to check if the user is logged in
124func loginRequired(next http.HandlerFunc) http.HandlerFunc {
125	return func(w http.ResponseWriter, r *http.Request) {
126		userID, err := readSessionCookie(r)
127		if err != nil {
128			http.Redirect(w, r, "/login", http.StatusFound)
129			return
130		}
131
132		ctx := context.WithValue(r.Context(), userContextKey, *userID)
133		next(w, r.WithContext(ctx))
134	}
135}
136
137func getLoggedUser(r *http.Request) (user User, ok bool) {
138	userID, ok := r.Context().Value(userContextKey).(uint)
139	db.Find(&user, userID)
140	return user, ok
141}
142
143func formatDuration(d time.Duration) string {
144	// TODO: 48h1m13s --> 2.01 days
145	return d.String()
146}
147
148func toHabitDisplay(habit Habit) HabitDisplay {
149	return HabitDisplay{
150		Name:     habit.Name,
151		LastAck:  formatDuration(time.Since(habit.LastAck)),
152		Disabled: habit.Disabled,
153		Class:    classGood,
154	}
155}
156
157func getAllHabits(userID uint) (positives []HabitDisplay, negatives []HabitDisplay, err error) {
158	var habits []Habit
159	err = db.Model(&Habit{}).Where(&Habit{UserID: userID}).Find(&habits).Error
160	if err != nil {
161		return
162	}
163
164	for _, habit := range habits {
165		habitDisplay := toHabitDisplay(habit)
166		if habit.Negative {
167			negatives = append(negatives, habitDisplay)
168		} else {
169			positives = append(positives, habitDisplay)
170		}
171	}
172	return
173}