From 9666f239feea99541a31243b6948496ed5673d82 Mon Sep 17 00:00:00 2001
From: Tim Wundenberg
Date: Mon, 2 Sep 2024 22:44:59 +0200
Subject: [PATCH] chore: #123 unify metrics, logs, variable names and structure
---
Dockerfile | 2 +-
handler.go | 12 +--
main.go | 9 ++-
middleware/cors.go | 4 +-
middleware/logger.go | 20 ++++-
.../001_initial_schema.up.sql | 0
.../002_user_and_session.up.sql | 0
service/auth.go | 80 +++++++------------
service/static_ui.go | 6 +-
service/workout.go | 50 +++++-------
template/index.templ | 2 +-
utils/db.go | 2 +-
utils/http-utils.go | 11 ---
13 files changed, 84 insertions(+), 114 deletions(-)
rename {migrations => migration}/001_initial_schema.up.sql (100%)
rename {migrations => migration}/002_user_and_session.up.sql (100%)
delete mode 100644 utils/http-utils.go
diff --git a/Dockerfile b/Dockerfile
index e4b1e11..62eeec2 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -16,7 +16,7 @@ WORKDIR /me-fit
RUN apt-get update && apt-get install -y ca-certificates && echo "" > .env
COPY --from=builder_go /me-fit/me-fit ./me-fit
COPY --from=builder_node /me-fit/static ./static
-COPY migrations ./migrations
+COPY migration ./migration
EXPOSE 8080
ENTRYPOINT ["/me-fit/me-fit"]
diff --git a/handler.go b/handler.go
index 7098725..1fa24bc 100644
--- a/handler.go
+++ b/handler.go
@@ -16,15 +16,15 @@ func getHandler(db *sql.DB) http.Handler {
// Serve static files (CSS, JS and images)
router.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("./static/"))))
- router.HandleFunc("/app", service.HandleWorkoutPage(db))
- router.HandleFunc("POST /api/workout", service.HandleNewWorkout(db))
- router.HandleFunc("GET /api/workout", service.HandleGetWorkouts(db))
- router.HandleFunc("DELETE /api/workout/{id}", service.HandleDeleteWorkout(db))
+ router.HandleFunc("/workout", service.HandleWorkoutPage(db))
+ router.HandleFunc("POST /api/workout", service.HandleWorkoutNewComp(db))
+ router.HandleFunc("GET /api/workout", service.HandleWorkoutGetComp(db))
+ router.HandleFunc("DELETE /api/workout/{id}", service.HandleWorkoutDeleteComp(db))
router.HandleFunc("/auth/signin", service.HandleSignInPage(db))
router.HandleFunc("/auth/signup", service.HandleSignUpPage(db))
- router.HandleFunc("/api/auth/signup", service.HandleSignUp(db))
- router.HandleFunc("/api/auth/signin", service.HandleSignIn(db))
+ router.HandleFunc("/api/auth/signup", service.HandleSignUpComp(db))
+ router.HandleFunc("/api/auth/signin", service.HandleSignInComp(db))
router.HandleFunc("/api/auth/signout", service.HandleSignOutComp(db))
return middleware.Logging(middleware.EnableCors(router))
diff --git a/main.go b/main.go
index eb9fd34..b567da9 100644
--- a/main.go
+++ b/main.go
@@ -1,10 +1,11 @@
package main
import (
+ "log"
"me-fit/utils"
"database/sql"
- "log"
+ "log/slog"
"net/http"
"github.com/joho/godotenv"
@@ -13,7 +14,7 @@ import (
)
func main() {
- log.Println("Starting server...")
+ slog.Info("Starting server...")
err := godotenv.Load()
if err != nil {
@@ -33,7 +34,7 @@ func main() {
Handler: promhttp.Handler(),
}
go func() {
- log.Println("Starting prometheus server at", prometheusServer.Addr)
+ slog.Info("Starting prometheus server on " + prometheusServer.Addr)
err := prometheusServer.ListenAndServe()
if err != nil {
panic(err)
@@ -44,7 +45,7 @@ func main() {
Addr: ":8080",
Handler: getHandler(db),
}
- log.Println("Starting server at", server.Addr)
+ slog.Info("Starting server on " + server.Addr)
err = server.ListenAndServe()
if err != nil {
diff --git a/middleware/cors.go b/middleware/cors.go
index 104e73f..05ac4ac 100644
--- a/middleware/cors.go
+++ b/middleware/cors.go
@@ -2,6 +2,7 @@ package middleware
import (
"log"
+ "log/slog"
"net/http"
"os"
)
@@ -11,12 +12,11 @@ func EnableCors(next http.Handler) http.Handler {
if base_url == "" {
log.Fatal("BASE_URL is not set")
}
- log.Println("BASE_URL is", base_url)
+ slog.Info("BASE_URL is " + base_url)
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", base_url)
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, DELETE")
- w.Header().Set("Access-Control-Allow-Headers", "Authorization")
if r.Method == "OPTIONS" {
w.WriteHeader(http.StatusOK)
diff --git a/middleware/logger.go b/middleware/logger.go
index a65cae3..6b55d99 100644
--- a/middleware/logger.go
+++ b/middleware/logger.go
@@ -1,9 +1,23 @@
package middleware
import (
- "log"
+ "log/slog"
"net/http"
+ "strconv"
"time"
+
+ "github.com/prometheus/client_golang/prometheus"
+ "github.com/prometheus/client_golang/prometheus/promauto"
+)
+
+var (
+ metrics = promauto.NewCounterVec(
+ prometheus.CounterOpts{
+ Name: "mefit_request_total",
+ Help: "The total number of requests processed",
+ },
+ []string{"path", "method", "status"},
+ )
)
type WrappedWriter struct {
@@ -25,6 +39,8 @@ func Logging(next http.Handler) http.Handler {
StatusCode: http.StatusOK,
}
next.ServeHTTP(wrapped, r)
- log.Println(r.RemoteAddr, wrapped.StatusCode, r.Method, r.URL.Path, time.Since(start))
+
+ slog.Info(r.RemoteAddr + " " + strconv.Itoa(wrapped.StatusCode) + " " + r.Method + " " + r.URL.Path + " " + time.Since(start).String())
+ metrics.WithLabelValues(r.URL.Path, r.Method, http.StatusText(wrapped.StatusCode)).Inc()
})
}
diff --git a/migrations/001_initial_schema.up.sql b/migration/001_initial_schema.up.sql
similarity index 100%
rename from migrations/001_initial_schema.up.sql
rename to migration/001_initial_schema.up.sql
diff --git a/migrations/002_user_and_session.up.sql b/migration/002_user_and_session.up.sql
similarity index 100%
rename from migrations/002_user_and_session.up.sql
rename to migration/002_user_and_session.up.sql
diff --git a/service/auth.go b/service/auth.go
index 55edcc0..faca684 100644
--- a/service/auth.go
+++ b/service/auth.go
@@ -5,7 +5,7 @@ import (
"crypto/subtle"
"database/sql"
"encoding/base64"
- "log"
+ "log/slog"
"net/http"
"net/mail"
"strings"
@@ -20,8 +20,8 @@ import (
)
type User struct {
- user_uuid uuid.UUID
- email string
+ id uuid.UUID
+ email string
}
func HandleSignInPage(db *sql.DB) http.HandlerFunc {
@@ -49,7 +49,7 @@ func UserInfoComp(user *User) templ.Component {
}
}
-func HandleSignUp(db *sql.DB) http.HandlerFunc {
+func HandleSignUpComp(db *sql.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var email = r.FormValue("email")
var password = r.FormValue("password")
@@ -69,9 +69,9 @@ func HandleSignUp(db *sql.DB) http.HandlerFunc {
return
}
- user_uuid, err := uuid.NewRandom()
+ userId, err := uuid.NewRandom()
if err != nil {
- log.Printf("Could not generate UUID: %v", err)
+ slog.Error("Could not generate UUID: %v", err)
auth.Error("Internal Server Error").Render(r.Context(), w)
return
}
@@ -79,14 +79,14 @@ func HandleSignUp(db *sql.DB) http.HandlerFunc {
salt := make([]byte, 16)
_, err = rand.Read(salt)
if err != nil {
- log.Printf("Could not generate salt: %v", err)
+ slog.Error("Could not generate salt: %v", err)
auth.Error("Internal Server Error").Render(r.Context(), w)
return
}
hash := getHashPassword(password, salt)
- _, err = db.Exec("INSERT INTO user (user_uuid, email, email_verified, is_admin, password, salt, created_at) VALUES (?, ?, FALSE, FALSE, ?, ?, datetime())", user_uuid, email, hash, salt)
+ _, err = db.Exec("INSERT INTO user (user_uuid, email, email_verified, is_admin, password, salt, created_at) VALUES (?, ?, FALSE, FALSE, ?, ?, datetime())", userId, email, hash, salt)
if err != nil {
// This does leak information about the email being in use, though not specifically stated
// It needs to be refacoteres to "If the email is not already in use, an email has been send to your address", or something
@@ -99,21 +99,20 @@ func HandleSignUp(db *sql.DB) http.HandlerFunc {
}
auth.Error("Internal Server Error").Render(r.Context(), w)
- log.Printf("Could not insert user: %v", err)
+ slog.Error("Could not insert user: %v", err)
return
}
- result := tryCreateSessionAndSetCookie(r, w, db, user_uuid)
+ result := tryCreateSessionAndSetCookie(r, w, db, userId)
if !result {
return
}
w.Header().Add("HX-Redirect", "/")
- w.WriteHeader(http.StatusOK)
}
}
-func HandleSignIn(db *sql.DB) http.HandlerFunc {
+func HandleSignInComp(db *sql.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var email = r.FormValue("email")
var password = r.FormValue("password")
@@ -121,10 +120,10 @@ func HandleSignIn(db *sql.DB) http.HandlerFunc {
var result bool = true
start := time.Now()
- var user_uuid uuid.UUID
- var saved_hash []byte
+ var userId uuid.UUID
+ var savedHash []byte
var salt []byte
- err := db.QueryRow("SELECT user_uuid, password, salt FROM user WHERE email = ?", email).Scan(&user_uuid, &saved_hash, &salt)
+ err := db.QueryRow("SELECT user_uuid, password, salt FROM user WHERE email = ?", email).Scan(&userId, &savedHash, &salt)
if err != nil {
result = false
}
@@ -132,26 +131,26 @@ func HandleSignIn(db *sql.DB) http.HandlerFunc {
if result {
new_hash := getHashPassword(password, salt)
- if subtle.ConstantTimeCompare(new_hash, saved_hash) == 0 {
+ if subtle.ConstantTimeCompare(new_hash, savedHash) == 0 {
result = false
}
}
if result {
- result := tryCreateSessionAndSetCookie(r, w, db, user_uuid)
+ result := tryCreateSessionAndSetCookie(r, w, db, userId)
if !result {
return
}
}
duration := time.Since(start)
- time_to_wait := 100 - duration.Milliseconds()
+ timeToWait := 100 - duration.Milliseconds()
// It is important to sleep for a while to prevent timing attacks
// If the email is correct, the server will calculate the hash, which will take some time
// This way an attacker could guess emails when comparing the response time
// Because of that, we cant use WriteHeader in the middle of the function. We have to wait until the end
// Unfortunatly this makes the code harder to read
- time.Sleep(time.Duration(time_to_wait) * time.Millisecond)
+ time.Sleep(time.Duration(timeToWait) * time.Millisecond)
if result {
w.Header().Add("HX-Redirect", "/")
@@ -168,7 +167,7 @@ func HandleSignOutComp(db *sql.DB) http.HandlerFunc {
_, err := db.Exec("DELETE FROM session WHERE session_id = ?", id)
if err != nil {
- log.Printf("Could not delete session: %v", err)
+ slog.Error("Could not delete session: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
@@ -188,36 +187,11 @@ func HandleSignOutComp(db *sql.DB) http.HandlerFunc {
}
}
-// var (
-// metricsAuthSignUp = promauto.NewCounterVec(
-// prometheus.CounterOpts{
-// Name: "mefit_api_auth_signup_total",
-// Help: "The total number of auth signup api requests processed",
-// },
-// []string{"result"},
-// )
-//
-// metricsError = promauto.NewCounterVec(
-// prometheus.CounterOpts{
-// Name: "mefit_api_error_total",
-// Help: "The total number of errors",
-// },
-// []string{"result"},
-// )
-//
-// // metricsAuthSignIn = promauto.NewCounterVec(
-// // prometheus.CounterOpts{
-// // Name: "mefit_api_auth_signin_total",
-// // },
-// // []string{"result"},
-// // )
-// )
-
func tryCreateSessionAndSetCookie(r *http.Request, w http.ResponseWriter, db *sql.DB, user_uuid uuid.UUID) bool {
var session_id_bytes []byte = make([]byte, 32)
_, err := rand.Reader.Read(session_id_bytes)
if err != nil {
- log.Printf("Could not generate session ID: %v", err)
+ slog.Error("Could not generate session ID: %v", err)
auth.Error("Internal Server Error").Render(r.Context(), w)
return false
}
@@ -225,7 +199,7 @@ func tryCreateSessionAndSetCookie(r *http.Request, w http.ResponseWriter, db *sq
_, err = db.Exec("INSERT INTO session (session_id, user_uuid, created_at) VALUES (?, ?, datetime())", session_id, user_uuid)
if err != nil {
- log.Printf("Could not insert session: %v", err)
+ slog.Error("Could not insert session: %v", err)
auth.Error("Internal Server Error").Render(r.Context(), w)
return false
}
@@ -254,25 +228,25 @@ func getSessionID(r *http.Request) string {
}
func verifySessionAndReturnUser(db *sql.DB, r *http.Request) *User {
- session_id := getSessionID(r)
- if session_id == "" {
+ sessionId := getSessionID(r)
+ if sessionId == "" {
return nil
}
var user User
- var created_at time.Time
+ var createdAt time.Time
err := db.QueryRow(`
SELECT u.user_uuid, u.email, s.created_at
FROM session s
INNER JOIN user u ON s.user_uuid = u.user_uuid
- WHERE session_id = ?`, session_id).Scan(&user.user_uuid, &user.email, &created_at)
+ WHERE session_id = ?`, sessionId).Scan(&user.id, &user.email, &createdAt)
if err != nil {
- log.Printf("Could not verify session: %v", err)
+ slog.Error("Could not verify session: " + err.Error())
return nil
}
- if created_at.Add(time.Duration(8 * time.Hour)).Before(time.Now()) {
+ if createdAt.Add(time.Duration(8 * time.Hour)).Before(time.Now()) {
return nil
}
diff --git a/service/static_ui.go b/service/static_ui.go
index d86efe8..644c419 100644
--- a/service/static_ui.go
+++ b/service/static_ui.go
@@ -11,13 +11,13 @@ import (
func HandleIndexAnd404(db *sql.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var comp templ.Component = nil
- user_comp := UserInfoComp(verifySessionAndReturnUser(db, r))
+ userComp := UserInfoComp(verifySessionAndReturnUser(db, r))
if r.URL.Path != "/" {
- comp = template.Layout(template.NotFound(), user_comp)
+ comp = template.Layout(template.NotFound(), userComp)
w.WriteHeader(http.StatusNotFound)
} else {
- comp = template.Layout(template.Index(), user_comp)
+ comp = template.Layout(template.Index(), userComp)
}
comp.Render(r.Context(), w)
diff --git a/service/workout.go b/service/workout.go
index 97f42a7..e0d5f7b 100644
--- a/service/workout.go
+++ b/service/workout.go
@@ -8,38 +8,28 @@ import (
"net/http"
"strconv"
"time"
-
- "github.com/prometheus/client_golang/prometheus"
- "github.com/prometheus/client_golang/prometheus/promauto"
-)
-
-var (
- metrics = promauto.NewCounterVec(
- prometheus.CounterOpts{
- Name: "mefit_api_workout_total",
- Help: "The total number of workout api requests processed",
- },
- []string{"type"},
- )
)
func HandleWorkoutPage(db *sql.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
+ user := verifySessionAndReturnUser(db, r)
+ if user == nil {
+ http.Redirect(w, r, "/auth/signin", http.StatusSeeOther)
+ return
+ }
+
currentDate := time.Now().Format("2006-01-02")
inner := workout.WorkoutComp(currentDate)
- user_comp := UserInfoComp(verifySessionAndReturnUser(db, r))
- layout := template.Layout(inner, user_comp)
- layout.Render(r.Context(), w)
+ userComp := UserInfoComp(user)
+ template.Layout(inner, userComp).Render(r.Context(), w)
}
}
-func HandleNewWorkout(db *sql.DB) http.HandlerFunc {
+func HandleWorkoutNewComp(db *sql.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
- metrics.WithLabelValues("new").Inc()
-
user := verifySessionAndReturnUser(db, r)
if user == nil {
- http.Error(w, "Unauthorized", http.StatusUnauthorized)
+ w.Header().Add("HX-Redirect", "/auth/signin")
return
}
@@ -71,7 +61,7 @@ func HandleNewWorkout(db *sql.DB) http.HandlerFunc {
return
}
- _, err = db.Exec("INSERT INTO workout (user_id, date, type, sets, reps) VALUES (?, ?, ?, ?, ?)", user.user_uuid, date, typeStr, sets, reps)
+ _, err = db.Exec("INSERT INTO workout (user_id, date, type, sets, reps) VALUES (?, ?, ?, ?, ?)", user.id, date, typeStr, sets, reps)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
@@ -88,17 +78,15 @@ func HandleNewWorkout(db *sql.DB) http.HandlerFunc {
}
}
-func HandleGetWorkouts(db *sql.DB) http.HandlerFunc {
+func HandleWorkoutGetComp(db *sql.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
- metrics.WithLabelValues("get").Inc()
-
user := verifySessionAndReturnUser(db, r)
if user == nil {
- http.Error(w, "Unauthorized", http.StatusUnauthorized)
+ w.Header().Add("HX-Redirect", "/auth/signin")
return
}
- rows, err := db.Query("SELECT rowid, date, type, sets, reps FROM workout WHERE user_id = ?", user.user_uuid)
+ rows, err := db.Query("SELECT rowid, date, type, sets, reps FROM workout WHERE user_id = ?", user.id)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
@@ -127,11 +115,13 @@ func HandleGetWorkouts(db *sql.DB) http.HandlerFunc {
}
}
-func HandleDeleteWorkout(db *sql.DB) http.HandlerFunc {
+func HandleWorkoutDeleteComp(db *sql.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
- metrics.WithLabelValues("delete").Inc()
-
user := verifySessionAndReturnUser(db, r)
+ if user == nil {
+ w.Header().Add("HX-Redirect", "/auth/signin")
+ return
+ }
rowId := r.PathValue("id")
if rowId == "" {
@@ -139,7 +129,7 @@ func HandleDeleteWorkout(db *sql.DB) http.HandlerFunc {
return
}
- res, err := db.Exec("DELETE FROM workout WHERE user_id = ? AND rowid = ?", user.user_uuid, rowId)
+ res, err := db.Exec("DELETE FROM workout WHERE user_id = ? AND rowid = ?", user.id, rowId)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
diff --git a/template/index.templ b/template/index.templ
index 51fd811..9b029a7 100644
--- a/template/index.templ
+++ b/template/index.templ
@@ -9,7 +9,7 @@ templ Index() {
Ever wanted to track your workouts and see your progress over time? ME-FIT is the perfect
solution for you.
- Get Started
+ Get Started
diff --git a/utils/db.go b/utils/db.go
index f105f88..7e2aa2a 100644
--- a/utils/db.go
+++ b/utils/db.go
@@ -16,7 +16,7 @@ func RunMigrations(db *sql.DB) {
}
m, err := migrate.NewWithDatabaseInstance(
- "file://./migrations/",
+ "file://./migration/",
"",
driver)
if err != nil {
diff --git a/utils/http-utils.go b/utils/http-utils.go
deleted file mode 100644
index 22017ef..0000000
--- a/utils/http-utils.go
+++ /dev/null
@@ -1,11 +0,0 @@
-package utils
-
-import (
- "encoding/json"
- "net/http"
-)
-
-func WriteJSON(w http.ResponseWriter, data interface{}) error {
- w.Header().Set("Content-Type", "application/json")
- return json.NewEncoder(w).Encode(data)
-}