Files
meal-prep-vibecoded/main.go
2025-10-25 15:55:25 +02:00

243 lines
7.3 KiB
Go

package main
import (
"fmt"
"html/template"
"log"
"mealprep/auth"
"mealprep/database"
"mealprep/handlers"
"net/http"
"strings"
)
func main() {
// Print startup banner
printBanner()
// Initialize database
if err := database.InitDB("mealprep.db"); err != nil {
log.Fatalf("Failed to initialize database: %v", err)
}
defer database.DB.Close()
log.Println("✅ Database initialized successfully")
// Static files
fs := http.FileServer(http.Dir("static"))
http.Handle("/static/", http.StripPrefix("/static/", fs))
// Authentication routes (public)
http.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) {
if r.Method == "GET" {
auth.RedirectIfAuthenticated(handlers.LoginPageHandler)(w, r)
} else if r.Method == "POST" {
handlers.LoginHandler(w, r)
} else {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
})
http.HandleFunc("/register", func(w http.ResponseWriter, r *http.Request) {
if r.Method == "GET" {
auth.RedirectIfAuthenticated(handlers.RegisterPageHandler)(w, r)
} else if r.Method == "POST" {
handlers.RegisterHandler(w, r)
} else {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
})
http.HandleFunc("/logout", handlers.LogoutHandler)
// Protected routes
http.HandleFunc("/", auth.RequireAuth(indexHandler))
// Ingredients (protected)
http.HandleFunc("/ingredients", auth.RequireAuth(func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case "GET":
handlers.IngredientsHandler(w, r)
case "POST":
handlers.AddIngredientHandler(w, r)
default:
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
}))
http.HandleFunc("/ingredients/", auth.RequireAuth(func(w http.ResponseWriter, r *http.Request) {
if r.Method == "DELETE" {
handlers.DeleteIngredientHandler(w, r)
} else {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
}))
// Meals (protected)
http.HandleFunc("/meals", auth.RequireAuth(func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case "GET":
handlers.MealsHandler(w, r)
case "POST":
handlers.AddMealHandler(w, r)
default:
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
}))
http.HandleFunc("/meals/", auth.RequireAuth(func(w http.ResponseWriter, r *http.Request) {
path := r.URL.Path
if strings.Contains(path, "/ingredients") {
// Meal ingredients routes
if r.Method == "GET" {
handlers.GetMealIngredientsHandler(w, r)
} else if r.Method == "POST" {
handlers.AddMealIngredientHandler(w, r)
} else if r.Method == "DELETE" {
handlers.DeleteMealIngredientHandler(w, r)
} else {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
} else {
// Meal delete route
if r.Method == "DELETE" {
handlers.DeleteMealHandler(w, r)
} else {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
}
}))
// Week Plan (protected)
http.HandleFunc("/week-plan", auth.RequireAuth(func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case "GET":
handlers.WeekPlanHandler(w, r)
case "POST":
handlers.AddWeekPlanEntryHandler(w, r)
default:
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
}))
http.HandleFunc("/week-plan/", auth.RequireAuth(func(w http.ResponseWriter, r *http.Request) {
if r.Method == "DELETE" {
handlers.DeleteWeekPlanEntryHandler(w, r)
} else {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
}))
// Grocery List (protected)
http.HandleFunc("/grocery-list", auth.RequireAuth(func(w http.ResponseWriter, r *http.Request) {
if r.Method == "GET" {
handlers.GroceryListHandler(w, r)
} else {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
}))
// Start server
port := "8080"
fmt.Println()
log.Printf("🚀 Server running on http://localhost:%s", port)
log.Println("📝 Press Ctrl+C to stop the server")
fmt.Println()
if err := http.ListenAndServe(":"+port, nil); err != nil {
log.Fatalf("❌ Failed to start server: %v", err)
}
}
func printBanner() {
banner := `
╔══════════════════════════════════════════════════════════════╗
║ ║
║ 🍽️ MEAL PREP PLANNER 🍽️ ║
║ ║
║ Plan your meals • Generate grocery lists ║
║ ║
╚══════════════════════════════════════════════════════════════╝
`
fmt.Println(banner)
log.Println("🔧 Initializing application...")
}
func indexHandler(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
http.NotFound(w, r)
return
}
tmpl := `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Meal Prep Planner</title>
<link rel="stylesheet" href="/static/styles.css">
<script src="https://unpkg.com/htmx.org@1.9.10"></script>
</head>
<body>
<header>
<div class="container">
<div class="header-content">
<h1>🍽️ Meal Prep Planner</h1>
<a href="/logout" class="logout-btn">Logout</a>
</div>
</div>
</header>
<div class="container">
<div class="tabs">
<button class="tab active"
hx-get="/ingredients"
hx-target="#content"
hx-swap="innerHTML"
onclick="setActiveTab(this)">
Ingredients
</button>
<button class="tab"
hx-get="/meals"
hx-target="#content"
hx-swap="innerHTML"
onclick="setActiveTab(this)">
Meals
</button>
<button class="tab"
hx-get="/week-plan"
hx-target="#content"
hx-swap="innerHTML"
onclick="setActiveTab(this)">
Week Plan
</button>
<button class="tab"
hx-get="/grocery-list"
hx-target="#content"
hx-swap="innerHTML"
onclick="setActiveTab(this)">
Grocery List
</button>
</div>
<div id="content" class="content">
<div hx-get="/ingredients" hx-trigger="load"></div>
</div>
</div>
<script>
function setActiveTab(clickedTab) {
// Remove active class from all tabs
document.querySelectorAll('.tab').forEach(tab => {
tab.classList.remove('active');
});
// Add active class to clicked tab
clickedTab.classList.add('active');
}
</script>
</body>
</html>
`
t := template.Must(template.New("index").Parse(tmpl))
if err := t.Execute(w, nil); err != nil {
log.Printf("Error rendering template: %v", err)
http.Error(w, "Internal server error", http.StatusInternalServerError)
}
}