package handlers import ( "html/template" "mealprep/auth" "mealprep/database" "net/http" "strconv" "strings" ) // IngredientsHandler handles the ingredients page func IngredientsHandler(w http.ResponseWriter, r *http.Request) { userID := auth.GetUserID(r) // Check if there's a search query searchTags := r.URL.Query().Get("tags") var ingredients []interface{} var err error if searchTags != "" { tagNames := strings.Split(searchTags, ",") for i := range tagNames { tagNames[i] = strings.TrimSpace(tagNames[i]) } ings, err := database.SearchIngredientsByTags(userID, tagNames) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } for _, ing := range ings { ingredients = append(ingredients, ing) } } else { ings, err := database.GetIngredientsWithTags(userID) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } for _, ing := range ings { ingredients = append(ingredients, ing) } } // Get all available tags for the filter UI (only tags that are in use) allTags, err := database.GetUsedTags(userID) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } tmpl := `

Ingredients

Filter by Tags

{{range .AllTags}} {{end}}
{{range .Ingredients}}
{{.Name}} ({{.Unit}})
{{range .Tags}} {{.Name}} {{end}}
{{end}}
` data := struct { Ingredients []interface{} AllTags []interface{} }{ Ingredients: ingredients, AllTags: []interface{}{}, } for _, tag := range allTags { data.AllTags = append(data.AllTags, tag) } t := template.Must(template.New("ingredients").Parse(tmpl)) t.Execute(w, data) } // 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 } name := strings.TrimSpace(r.FormValue("name")) unit := strings.TrimSpace(r.FormValue("unit")) tagsStr := strings.TrimSpace(r.FormValue("tags")) if name == "" || unit == "" { http.Error(w, "Name and unit are required", http.StatusBadRequest) return } id, err := database.AddIngredient(userID, name, unit) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } // Process tags if provided var tags []string if tagsStr != "" { tagNames := strings.Split(tagsStr, ",") for _, tagName := range tagNames { tagName = strings.TrimSpace(tagName) if tagName != "" { // Get or create tag tagID, err := database.GetOrCreateTag(userID, tagName) if err != nil { continue // Skip this tag on error } // Link tag to ingredient database.AddIngredientTag(userID, int(id), int(tagID)) tags = append(tags, tagName) } } } tmpl := `
{{.Name}} ({{.Unit}})
{{range .Tags}} {{.}} {{end}}
` data := struct { ID int64 Name string Unit string Tags []string }{id, name, unit, tags} t := template.Must(template.New("ingredient").Parse(tmpl)) t.Execute(w, data) } // 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 { http.Error(w, "Invalid ID", http.StatusBadRequest) return } if err := database.DeleteIngredient(userID, id); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.WriteHeader(http.StatusOK) } // GetIngredientTagsHandler returns the tag editing UI for an ingredient func GetIngredientTagsHandler(w http.ResponseWriter, r *http.Request) { userID := auth.GetUserID(r) // Extract ingredient ID from path /ingredients/{id}/tags pathParts := strings.Split(strings.TrimPrefix(r.URL.Path, "/ingredients/"), "/") if len(pathParts) < 1 { http.Error(w, "Invalid path", http.StatusBadRequest) return } ingredientID, err := strconv.Atoi(pathParts[0]) if err != nil { http.Error(w, "Invalid ingredient ID", http.StatusBadRequest) return } // Get current tags for ingredient currentTags, err := database.GetIngredientTags(userID, ingredientID) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } // Get all available tags allTags, err := database.GetAllTags(userID) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } // Build a set of current tag IDs for easy lookup currentTagIDs := make(map[int]bool) for _, tag := range currentTags { currentTagIDs[tag.ID] = true } tmpl := `

Edit Tags

Current Tags

{{range .CurrentTags}} {{.Name}} {{end}}

Add Tags

{{range .AllTags}} {{if not (index $.CurrentTagIDs .ID)}}
` data := struct { IngredientID int CurrentTags []interface{} AllTags []interface{} CurrentTagIDs map[int]bool }{ IngredientID: ingredientID, CurrentTags: []interface{}{}, AllTags: []interface{}{}, CurrentTagIDs: currentTagIDs, } for _, tag := range currentTags { data.CurrentTags = append(data.CurrentTags, tag) } for _, tag := range allTags { data.AllTags = append(data.AllTags, tag) } t := template.Must(template.New("ingredient-tags").Parse(tmpl)) t.Execute(w, data) } // AddIngredientTagHandler adds a tag to an ingredient func AddIngredientTagHandler(w http.ResponseWriter, r *http.Request) { userID := auth.GetUserID(r) // Extract ingredient ID from path pathParts := strings.Split(strings.TrimPrefix(r.URL.Path, "/ingredients/"), "/") if len(pathParts) < 1 { http.Error(w, "Invalid path", http.StatusBadRequest) return } ingredientID, err := strconv.Atoi(pathParts[0]) if err != nil { http.Error(w, "Invalid ingredient ID", http.StatusBadRequest) return } if err := r.ParseForm(); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } tagName := strings.TrimSpace(r.FormValue("tag_name")) if tagName == "" { http.Error(w, "Tag name is required", http.StatusBadRequest) return } // Get or create the tag tagID, err := database.GetOrCreateTag(userID, tagName) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } // Add tag to ingredient if err := database.AddIngredientTag(userID, ingredientID, int(tagID)); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } // Return updated tag list with trigger to refresh page w.Header().Set("HX-Trigger", "refreshIngredients") renderCurrentTags(w, userID, ingredientID) } // RemoveIngredientTagHandler removes a tag from an ingredient func RemoveIngredientTagHandler(w http.ResponseWriter, r *http.Request) { userID := auth.GetUserID(r) // Extract ingredient ID and tag ID from path /ingredients/{ingredient_id}/tags/{tag_id} pathParts := strings.Split(strings.TrimPrefix(r.URL.Path, "/ingredients/"), "/") if len(pathParts) < 3 { http.Error(w, "Invalid path", http.StatusBadRequest) return } ingredientID, err := strconv.Atoi(pathParts[0]) if err != nil { http.Error(w, "Invalid ingredient ID", http.StatusBadRequest) return } tagID, err := strconv.Atoi(pathParts[2]) if err != nil { http.Error(w, "Invalid tag ID", http.StatusBadRequest) return } // Remove tag from ingredient if err := database.RemoveIngredientTag(userID, ingredientID, tagID); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } // Return updated tag list with trigger to refresh page w.Header().Set("HX-Trigger", "refreshIngredients") renderCurrentTags(w, userID, ingredientID) } // Helper function to render current tags func renderCurrentTags(w http.ResponseWriter, userID, ingredientID int) { currentTags, err := database.GetIngredientTags(userID, ingredientID) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } tmpl := ` {{range .Tags}} {{.Name}} {{end}} ` type TagWithID struct { ID int Name string } data := struct { Tags []TagWithID IngredientID int }{ Tags: []TagWithID{}, IngredientID: ingredientID, } for _, tag := range currentTags { data.Tags = append(data.Tags, TagWithID{ID: tag.ID, Name: tag.Name}) } t := template.Must(template.New("current-tags").Parse(tmpl)) t.Execute(w, data) }