added filter and tag for ingredients
This commit is contained in:
267
database/db.go
267
database/db.go
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user