added account

This commit is contained in:
2025-10-25 15:55:25 +02:00
parent 72c50549d7
commit 4db5084bc6
15 changed files with 1214 additions and 150 deletions

300
handlers/auth.go Normal file
View File

@@ -0,0 +1,300 @@
package handlers
import (
"database/sql"
"html/template"
"mealprep/auth"
"mealprep/database"
"net/http"
"strings"
"time"
)
// LoginPageHandler shows the login page
func LoginPageHandler(w http.ResponseWriter, r *http.Request) {
tmpl := `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login - Meal Prep Planner</title>
<link rel="stylesheet" href="/static/styles.css">
</head>
<body>
<div class="auth-container">
<div class="auth-card">
<h1>🍽️ Meal Prep Planner</h1>
<h2>Login</h2>
{{if .Error}}
<div class="error-message">{{.Error}}</div>
{{end}}
<form method="POST" action="/login" class="auth-form">
<div class="form-group">
<label>Email:</label>
<input type="email" name="email" required autofocus />
</div>
<div class="form-group">
<label>Password:</label>
<input type="password" name="password" required />
</div>
<button type="submit" class="btn-primary">Login</button>
</form>
<p class="auth-link">
Don't have an account? <a href="/register">Register here</a>
</p>
</div>
</div>
</body>
</html>
`
data := struct {
Error string
}{
Error: r.URL.Query().Get("error"),
}
t := template.Must(template.New("login").Parse(tmpl))
t.Execute(w, data)
}
// RegisterPageHandler shows the registration page
func RegisterPageHandler(w http.ResponseWriter, r *http.Request) {
tmpl := `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Register - Meal Prep Planner</title>
<link rel="stylesheet" href="/static/styles.css">
</head>
<body>
<div class="auth-container">
<div class="auth-card">
<h1>🍽️ Meal Prep Planner</h1>
<h2>Create Account</h2>
{{if .Error}}
<div class="error-message">{{.Error}}</div>
{{end}}
<form method="POST" action="/register" class="auth-form">
<div class="form-group">
<label>Email:</label>
<input type="email" name="email" required autofocus />
</div>
<div class="form-group">
<label>Password (min 8 characters):</label>
<input type="password" name="password" required minlength="8" />
</div>
<div class="form-group">
<label>Confirm Password:</label>
<input type="password" name="password_confirm" required minlength="8" />
</div>
<button type="submit" class="btn-primary">Create Account</button>
</form>
<p class="auth-link">
Already have an account? <a href="/login">Login here</a>
</p>
</div>
</div>
</body>
</html>
`
data := struct {
Error string
}{
Error: r.URL.Query().Get("error"),
}
t := template.Must(template.New("register").Parse(tmpl))
t.Execute(w, data)
}
// LoginHandler processes login requests
func LoginHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.Redirect(w, r, "/login", http.StatusSeeOther)
return
}
if err := r.ParseForm(); err != nil {
http.Redirect(w, r, "/login?error=Invalid+request", http.StatusSeeOther)
return
}
email := strings.TrimSpace(strings.ToLower(r.FormValue("email")))
password := r.FormValue("password")
// Validate input
if email == "" || password == "" {
http.Redirect(w, r, "/login?error=Email+and+password+required", http.StatusSeeOther)
return
}
if !auth.ValidateEmail(email) {
http.Redirect(w, r, "/login?error=Invalid+email+format", http.StatusSeeOther)
return
}
// Get user from database
user, err := database.GetUserByEmail(email)
if err != nil {
if err == sql.ErrNoRows {
http.Redirect(w, r, "/login?error=Invalid+credentials", http.StatusSeeOther)
return
}
http.Redirect(w, r, "/login?error=Server+error", http.StatusSeeOther)
return
}
// Check password
if !auth.CheckPassword(password, user.PasswordHash) {
http.Redirect(w, r, "/login?error=Invalid+credentials", http.StatusSeeOther)
return
}
// Create session
token, err := auth.GenerateSessionToken()
if err != nil {
http.Redirect(w, r, "/login?error=Failed+to+create+session", http.StatusSeeOther)
return
}
expiresAt := auth.GetSessionExpiry()
if err := database.CreateSession(token, user.ID, expiresAt); err != nil {
http.Redirect(w, r, "/login?error=Failed+to+create+session", http.StatusSeeOther)
return
}
// Set secure cookie
http.SetCookie(w, &http.Cookie{
Name: "session_token",
Value: token,
Expires: expiresAt,
HttpOnly: true,
Secure: false, // Set to true in production with HTTPS
SameSite: http.SameSiteStrictMode,
Path: "/",
})
// Redirect to home
http.Redirect(w, r, "/", http.StatusSeeOther)
}
// RegisterHandler processes registration requests
func RegisterHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.Redirect(w, r, "/register", http.StatusSeeOther)
return
}
if err := r.ParseForm(); err != nil {
http.Redirect(w, r, "/register?error=Invalid+request", http.StatusSeeOther)
return
}
email := strings.TrimSpace(strings.ToLower(r.FormValue("email")))
password := r.FormValue("password")
passwordConfirm := r.FormValue("password_confirm")
// Validate input
if email == "" || password == "" || passwordConfirm == "" {
http.Redirect(w, r, "/register?error=All+fields+required", http.StatusSeeOther)
return
}
if !auth.ValidateEmail(email) {
http.Redirect(w, r, "/register?error=Invalid+email+format", http.StatusSeeOther)
return
}
if password != passwordConfirm {
http.Redirect(w, r, "/register?error=Passwords+do+not+match", http.StatusSeeOther)
return
}
if len(password) < 8 {
http.Redirect(w, r, "/register?error=Password+must+be+at+least+8+characters", http.StatusSeeOther)
return
}
// Check if user exists
existingUser, _ := database.GetUserByEmail(email)
if existingUser != nil {
http.Redirect(w, r, "/register?error=Email+already+registered", http.StatusSeeOther)
return
}
// Hash password
passwordHash, err := auth.HashPassword(password)
if err != nil {
http.Redirect(w, r, "/register?error=Failed+to+create+account", http.StatusSeeOther)
return
}
// Create user
userID, err := database.CreateUser(email, passwordHash)
if err != nil {
http.Redirect(w, r, "/register?error=Failed+to+create+account", http.StatusSeeOther)
return
}
// Create session
token, err := auth.GenerateSessionToken()
if err != nil {
http.Redirect(w, r, "/login", http.StatusSeeOther)
return
}
expiresAt := auth.GetSessionExpiry()
if err := database.CreateSession(token, int(userID), expiresAt); err != nil {
http.Redirect(w, r, "/login", http.StatusSeeOther)
return
}
// Set secure cookie
http.SetCookie(w, &http.Cookie{
Name: "session_token",
Value: token,
Expires: expiresAt,
HttpOnly: true,
Secure: false, // Set to true in production with HTTPS
SameSite: http.SameSiteStrictMode,
Path: "/",
})
// Redirect to home
http.Redirect(w, r, "/", http.StatusSeeOther)
}
// LogoutHandler processes logout requests
func LogoutHandler(w http.ResponseWriter, r *http.Request) {
// Get session cookie
cookie, err := r.Cookie("session_token")
if err == nil {
// Delete session from database
database.DeleteSession(cookie.Value)
}
// Clear cookie
http.SetCookie(w, &http.Cookie{
Name: "session_token",
Value: "",
Expires: time.Unix(0, 0),
HttpOnly: true,
Secure: false,
SameSite: http.SameSiteStrictMode,
Path: "/",
MaxAge: -1,
})
http.Redirect(w, r, "/login", http.StatusSeeOther)
}

View File

@@ -2,13 +2,15 @@ package handlers
import (
"html/template"
"mealprep/auth"
"mealprep/database"
"net/http"
)
// GroceryListHandler handles the grocery list page
func GroceryListHandler(w http.ResponseWriter, r *http.Request) {
groceryItems, err := database.GetGroceryList()
userID := auth.GetUserID(r)
groceryItems, err := database.GetGroceryList(userID)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return

View File

@@ -2,6 +2,7 @@ package handlers
import (
"html/template"
"mealprep/auth"
"mealprep/database"
"net/http"
"strconv"
@@ -10,7 +11,8 @@ import (
// IngredientsHandler handles the ingredients page
func IngredientsHandler(w http.ResponseWriter, r *http.Request) {
ingredients, err := database.GetAllIngredients()
userID := auth.GetUserID(r)
ingredients, err := database.GetAllIngredients(userID)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
@@ -51,6 +53,8 @@ func IngredientsHandler(w http.ResponseWriter, r *http.Request) {
// AddIngredientHandler handles adding a new ingredient
func AddIngredientHandler(w http.ResponseWriter, r *http.Request) {
userID := auth.GetUserID(r)
if err := r.ParseForm(); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
@@ -64,7 +68,7 @@ func AddIngredientHandler(w http.ResponseWriter, r *http.Request) {
return
}
id, err := database.AddIngredient(name, unit)
id, err := database.AddIngredient(userID, name, unit)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
@@ -97,6 +101,8 @@ func AddIngredientHandler(w http.ResponseWriter, r *http.Request) {
// DeleteIngredientHandler handles deleting an ingredient
func DeleteIngredientHandler(w http.ResponseWriter, r *http.Request) {
userID := auth.GetUserID(r)
idStr := strings.TrimPrefix(r.URL.Path, "/ingredients/")
id, err := strconv.Atoi(idStr)
if err != nil {
@@ -104,7 +110,7 @@ func DeleteIngredientHandler(w http.ResponseWriter, r *http.Request) {
return
}
if err := database.DeleteIngredient(id); err != nil {
if err := database.DeleteIngredient(userID, id); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}

View File

@@ -2,6 +2,7 @@ package handlers
import (
"html/template"
"mealprep/auth"
"mealprep/database"
"net/http"
"strconv"
@@ -10,7 +11,8 @@ import (
// MealsHandler handles the meals page
func MealsHandler(w http.ResponseWriter, r *http.Request) {
meals, err := database.GetAllMeals()
userID := auth.GetUserID(r)
meals, err := database.GetAllMeals(userID)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
@@ -72,6 +74,8 @@ func MealsHandler(w http.ResponseWriter, r *http.Request) {
// AddMealHandler handles adding a new meal
func AddMealHandler(w http.ResponseWriter, r *http.Request) {
userID := auth.GetUserID(r)
if err := r.ParseForm(); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
@@ -92,7 +96,7 @@ func AddMealHandler(w http.ResponseWriter, r *http.Request) {
return
}
id, err := database.AddMeal(name, description, mealType)
id, err := database.AddMeal(userID, name, description, mealType)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
@@ -141,6 +145,8 @@ func AddMealHandler(w http.ResponseWriter, r *http.Request) {
// DeleteMealHandler handles deleting a meal
func DeleteMealHandler(w http.ResponseWriter, r *http.Request) {
userID := auth.GetUserID(r)
idStr := strings.TrimPrefix(r.URL.Path, "/meals/")
id, err := strconv.Atoi(idStr)
if err != nil {
@@ -148,7 +154,7 @@ func DeleteMealHandler(w http.ResponseWriter, r *http.Request) {
return
}
if err := database.DeleteMeal(id); err != nil {
if err := database.DeleteMeal(userID, id); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
@@ -158,6 +164,8 @@ func DeleteMealHandler(w http.ResponseWriter, r *http.Request) {
// GetMealIngredientsHandler shows ingredients for a specific meal
func GetMealIngredientsHandler(w http.ResponseWriter, r *http.Request) {
userID := auth.GetUserID(r)
parts := strings.Split(r.URL.Path, "/")
if len(parts) < 3 {
http.Error(w, "Invalid URL", http.StatusBadRequest)
@@ -170,13 +178,13 @@ func GetMealIngredientsHandler(w http.ResponseWriter, r *http.Request) {
return
}
mealIngredients, err := database.GetMealIngredients(mealID)
mealIngredients, err := database.GetMealIngredients(userID, mealID)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
allIngredients, err := database.GetAllIngredients()
allIngredients, err := database.GetAllIngredients(userID)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
@@ -233,6 +241,8 @@ func GetMealIngredientsHandler(w http.ResponseWriter, r *http.Request) {
// AddMealIngredientHandler adds an ingredient to a meal
func AddMealIngredientHandler(w http.ResponseWriter, r *http.Request) {
userID := auth.GetUserID(r)
parts := strings.Split(r.URL.Path, "/")
if len(parts) < 3 {
http.Error(w, "Invalid URL", http.StatusBadRequest)
@@ -262,13 +272,13 @@ func AddMealIngredientHandler(w http.ResponseWriter, r *http.Request) {
return
}
if err := database.AddMealIngredient(mealID, ingredientID, quantity); err != nil {
if err := database.AddMealIngredient(userID, mealID, ingredientID, quantity); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// Get the ingredient details to display
ingredients, err := database.GetMealIngredients(mealID)
ingredients, err := database.GetMealIngredients(userID, mealID)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
@@ -308,6 +318,8 @@ func AddMealIngredientHandler(w http.ResponseWriter, r *http.Request) {
// DeleteMealIngredientHandler removes an ingredient from a meal
func DeleteMealIngredientHandler(w http.ResponseWriter, r *http.Request) {
userID := auth.GetUserID(r)
parts := strings.Split(r.URL.Path, "/")
if len(parts) < 5 {
http.Error(w, "Invalid URL", http.StatusBadRequest)
@@ -326,7 +338,7 @@ func DeleteMealIngredientHandler(w http.ResponseWriter, r *http.Request) {
return
}
if err := database.DeleteMealIngredient(mealID, ingredientID); err != nil {
if err := database.DeleteMealIngredient(userID, mealID, ingredientID); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}

View File

@@ -2,6 +2,7 @@ package handlers
import (
"html/template"
"mealprep/auth"
"mealprep/database"
"net/http"
"strconv"
@@ -11,13 +12,15 @@ import (
// WeekPlanHandler handles the week plan page
func WeekPlanHandler(w http.ResponseWriter, r *http.Request) {
weekPlan, err := database.GetWeekPlan()
userID := auth.GetUserID(r)
weekPlan, err := database.GetWeekPlan(userID)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
meals, err := database.GetAllMeals()
meals, err := database.GetAllMeals(userID)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
@@ -224,6 +227,8 @@ func WeekPlanHandler(w http.ResponseWriter, r *http.Request) {
// AddWeekPlanEntryHandler adds a meal to a specific day
func AddWeekPlanEntryHandler(w http.ResponseWriter, r *http.Request) {
userID := auth.GetUserID(r)
if err := r.ParseForm(); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
@@ -249,13 +254,13 @@ func AddWeekPlanEntryHandler(w http.ResponseWriter, r *http.Request) {
return
}
if err := database.AddWeekPlanEntry(date, mealID); err != nil {
if err := database.AddWeekPlanEntry(userID, date, mealID); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// Get the meal details
meal, err := database.GetMealByID(mealID)
meal, err := database.GetMealByID(userID, mealID)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
@@ -268,7 +273,7 @@ func AddWeekPlanEntryHandler(w http.ResponseWriter, r *http.Request) {
}
// Get the ID of the entry we just added
weekPlan, err := database.GetWeekPlan()
weekPlan, err := database.GetWeekPlan(userID)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
@@ -305,6 +310,8 @@ func AddWeekPlanEntryHandler(w http.ResponseWriter, r *http.Request) {
// DeleteWeekPlanEntryHandler removes a meal from the week plan
func DeleteWeekPlanEntryHandler(w http.ResponseWriter, r *http.Request) {
userID := auth.GetUserID(r)
idStr := strings.TrimPrefix(r.URL.Path, "/week-plan/")
id, err := strconv.Atoi(idStr)
if err != nil {
@@ -312,7 +319,7 @@ func DeleteWeekPlanEntryHandler(w http.ResponseWriter, r *http.Request) {
return
}
if err := database.DeleteWeekPlanEntry(id); err != nil {
if err := database.DeleteWeekPlanEntry(userID, id); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}