added filter and tag for ingredients

This commit is contained in:
2025-10-25 22:13:55 +02:00
parent b8046c87b9
commit cc28aa9a8e
5 changed files with 914 additions and 29 deletions

View File

@@ -4,6 +4,7 @@ import (
"database/sql"
"fmt"
"mealprep/models"
"strings"
"time"
_ "github.com/mattn/go-sqlite3"
@@ -71,6 +72,26 @@ func createTables() error {
CREATE INDEX IF NOT EXISTS idx_ingredients_user_id ON ingredients(user_id);
-- Tags table (user-isolated)
CREATE TABLE IF NOT EXISTS tags (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
name TEXT NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
UNIQUE(user_id, name)
);
CREATE INDEX IF NOT EXISTS idx_tags_user_id ON tags(user_id);
-- Ingredient tags junction table
CREATE TABLE IF NOT EXISTS ingredient_tags (
ingredient_id INTEGER NOT NULL,
tag_id INTEGER NOT NULL,
PRIMARY KEY (ingredient_id, tag_id),
FOREIGN KEY (ingredient_id) REFERENCES ingredients(id) ON DELETE CASCADE,
FOREIGN KEY (tag_id) REFERENCES tags(id) ON DELETE CASCADE
);
-- Meals table (user-isolated)
CREATE TABLE IF NOT EXISTS meals (
id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -248,7 +269,14 @@ func DeleteIngredient(userID, ingredientID int) error {
"DELETE FROM ingredients WHERE id = ? AND user_id = ?",
ingredientID, userID,
)
return err
if err != nil {
return err
}
// Clean up unused tags
CleanupUnusedTags(userID)
return nil
}
// Meal operations (user-isolated)
@@ -454,3 +482,240 @@ func GetGroceryList(userID int) ([]models.GroceryItem, error) {
}
return items, nil
}
// Tag operations (user-isolated)
func GetAllTags(userID int) ([]models.Tag, error) {
rows, err := DB.Query(
"SELECT id, user_id, name FROM tags WHERE user_id = ? ORDER BY name",
userID,
)
if err != nil {
return nil, err
}
defer rows.Close()
var tags []models.Tag
for rows.Next() {
var tag models.Tag
if err := rows.Scan(&tag.ID, &tag.UserID, &tag.Name); err != nil {
return nil, err
}
tags = append(tags, tag)
}
return tags, nil
}
func GetUsedTags(userID int) ([]models.Tag, error) {
query := `
SELECT DISTINCT t.id, t.user_id, t.name
FROM tags t
JOIN ingredient_tags it ON t.id = it.tag_id
WHERE t.user_id = ?
ORDER BY t.name
`
rows, err := DB.Query(query, userID)
if err != nil {
return nil, err
}
defer rows.Close()
var tags []models.Tag
for rows.Next() {
var tag models.Tag
if err := rows.Scan(&tag.ID, &tag.UserID, &tag.Name); err != nil {
return nil, err
}
tags = append(tags, tag)
}
return tags, nil
}
func CleanupUnusedTags(userID int) error {
// Delete tags that have no ingredient associations
_, err := DB.Exec(`
DELETE FROM tags
WHERE user_id = ?
AND id NOT IN (
SELECT DISTINCT tag_id FROM ingredient_tags
)
`, userID)
return err
}
func AddTag(userID int, name string) (int64, error) {
result, err := DB.Exec(
"INSERT INTO tags (user_id, name) VALUES (?, ?)",
userID, name,
)
if err != nil {
return 0, err
}
return result.LastInsertId()
}
func GetOrCreateTag(userID int, name string) (int64, error) {
// Try to get existing tag
var tagID int64
err := DB.QueryRow(
"SELECT id FROM tags WHERE user_id = ? AND name = ?",
userID, name,
).Scan(&tagID)
if err == sql.ErrNoRows {
// Tag doesn't exist, create it
return AddTag(userID, name)
}
if err != nil {
return 0, err
}
return tagID, nil
}
func DeleteTag(userID, tagID int) error {
_, err := DB.Exec(
"DELETE FROM tags WHERE id = ? AND user_id = ?",
tagID, userID,
)
return err
}
// Ingredient tag operations
func GetIngredientTags(userID, ingredientID int) ([]models.Tag, error) {
query := `
SELECT t.id, t.user_id, t.name
FROM tags t
JOIN ingredient_tags it ON t.id = it.tag_id
JOIN ingredients i ON it.ingredient_id = i.id
WHERE it.ingredient_id = ? AND i.user_id = ? AND t.user_id = ?
ORDER BY t.name
`
rows, err := DB.Query(query, ingredientID, userID, userID)
if err != nil {
return nil, err
}
defer rows.Close()
var tags []models.Tag
for rows.Next() {
var tag models.Tag
if err := rows.Scan(&tag.ID, &tag.UserID, &tag.Name); err != nil {
return nil, err
}
tags = append(tags, tag)
}
return tags, nil
}
func AddIngredientTag(userID, ingredientID, tagID int) error {
// Verify ingredient and tag belong to user
var count int
err := DB.QueryRow(
"SELECT COUNT(*) FROM ingredients i, tags t WHERE i.id = ? AND t.id = ? AND i.user_id = ? AND t.user_id = ?",
ingredientID, tagID, userID, userID,
).Scan(&count)
if err != nil || count == 0 {
return fmt.Errorf("ingredient or tag not found or doesn't belong to user")
}
_, err = DB.Exec(
"INSERT OR IGNORE INTO ingredient_tags (ingredient_id, tag_id) VALUES (?, ?)",
ingredientID, tagID,
)
return err
}
func RemoveIngredientTag(userID, ingredientID, tagID int) error {
// Verify ownership
var count int
err := DB.QueryRow(
"SELECT COUNT(*) FROM ingredients WHERE id = ? AND user_id = ?",
ingredientID, userID,
).Scan(&count)
if err != nil || count == 0 {
return fmt.Errorf("ingredient not found or doesn't belong to user")
}
_, err = DB.Exec(
"DELETE FROM ingredient_tags WHERE ingredient_id = ? AND tag_id = ?",
ingredientID, tagID,
)
if err != nil {
return err
}
// Clean up unused tags
CleanupUnusedTags(userID)
return nil
}
func GetIngredientsWithTags(userID int) ([]models.Ingredient, error) {
rows, err := DB.Query(
"SELECT id, user_id, name, unit FROM ingredients WHERE user_id = ? ORDER BY name",
userID,
)
if err != nil {
return nil, err
}
defer rows.Close()
var ingredients []models.Ingredient
for rows.Next() {
var ing models.Ingredient
if err := rows.Scan(&ing.ID, &ing.UserID, &ing.Name, &ing.Unit); err != nil {
return nil, err
}
// Get tags for this ingredient
ing.Tags, _ = GetIngredientTags(userID, ing.ID)
ingredients = append(ingredients, ing)
}
return ingredients, nil
}
func SearchIngredientsByTags(userID int, tagNames []string) ([]models.Ingredient, error) {
if len(tagNames) == 0 {
return GetIngredientsWithTags(userID)
}
// Build query with placeholders for tag names
placeholders := make([]string, len(tagNames))
args := []interface{}{userID}
for i, name := range tagNames {
placeholders[i] = "?"
args = append(args, name)
}
args = append(args, len(tagNames))
query := fmt.Sprintf(`
SELECT DISTINCT i.id, i.user_id, i.name, i.unit
FROM ingredients i
JOIN ingredient_tags it ON i.id = it.ingredient_id
JOIN tags t ON it.tag_id = t.id
WHERE i.user_id = ? AND t.name IN (%s)
GROUP BY i.id
HAVING COUNT(DISTINCT t.id) = ?
ORDER BY i.name
`, strings.Join(placeholders, ","))
rows, err := DB.Query(query, args...)
if err != nil {
return nil, err
}
defer rows.Close()
var ingredients []models.Ingredient
for rows.Next() {
var ing models.Ingredient
if err := rows.Scan(&ing.ID, &ing.UserID, &ing.Name, &ing.Unit); err != nil {
return nil, err
}
// Get tags for this ingredient
ing.Tags, _ = GetIngredientTags(userID, ing.ID)
ingredients = append(ingredients, ing)
}
return ingredients, nil
}