added account
This commit is contained in:
265
database/db.go
265
database/db.go
@@ -29,29 +29,56 @@ func InitDB(dbPath string) error {
|
||||
return fmt.Errorf("failed to create tables: %w", err)
|
||||
}
|
||||
|
||||
// Run migrations
|
||||
if err = runMigrations(); err != nil {
|
||||
return fmt.Errorf("failed to run migrations: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func createTables() error {
|
||||
schema := `
|
||||
-- Users table
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
email TEXT NOT NULL UNIQUE,
|
||||
password_hash TEXT NOT NULL,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Sessions table
|
||||
CREATE TABLE IF NOT EXISTS sessions (
|
||||
token TEXT PRIMARY KEY,
|
||||
user_id INTEGER NOT NULL,
|
||||
expires_at DATETIME NOT NULL,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_sessions_user_id ON sessions(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_sessions_expires_at ON sessions(expires_at);
|
||||
|
||||
-- Ingredients table (user-isolated)
|
||||
CREATE TABLE IF NOT EXISTS ingredients (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
unit TEXT NOT NULL
|
||||
user_id INTEGER NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
unit TEXT NOT NULL,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
UNIQUE(user_id, name)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_ingredients_user_id ON ingredients(user_id);
|
||||
|
||||
-- Meals table (user-isolated)
|
||||
CREATE TABLE IF NOT EXISTS meals (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
description TEXT,
|
||||
meal_type TEXT NOT NULL DEFAULT 'lunch'
|
||||
meal_type TEXT NOT NULL DEFAULT 'lunch',
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_meals_user_id ON meals(user_id);
|
||||
|
||||
-- Meal ingredients
|
||||
CREATE TABLE IF NOT EXISTS meal_ingredients (
|
||||
meal_id INTEGER NOT NULL,
|
||||
ingredient_id INTEGER NOT NULL,
|
||||
@@ -61,41 +88,99 @@ func createTables() error {
|
||||
FOREIGN KEY (ingredient_id) REFERENCES ingredients(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Week plan
|
||||
CREATE TABLE IF NOT EXISTS week_plan (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
date TEXT NOT NULL,
|
||||
meal_id INTEGER NOT NULL,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (meal_id) REFERENCES meals(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_week_plan_user_id ON week_plan(user_id);
|
||||
`
|
||||
|
||||
_, err := DB.Exec(schema)
|
||||
return err
|
||||
}
|
||||
|
||||
func runMigrations() error {
|
||||
// Check if meal_type column exists
|
||||
var count int
|
||||
err := DB.QueryRow("SELECT COUNT(*) FROM pragma_table_info('meals') WHERE name='meal_type'").Scan(&count)
|
||||
// User operations
|
||||
|
||||
func CreateUser(email, passwordHash string) (int64, error) {
|
||||
result, err := DB.Exec(
|
||||
"INSERT INTO users (email, password_hash) VALUES (?, ?)",
|
||||
email, passwordHash,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Add meal_type column if it doesn't exist
|
||||
if count == 0 {
|
||||
_, err = DB.Exec("ALTER TABLE meals ADD COLUMN meal_type TEXT NOT NULL DEFAULT 'lunch'")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return result.LastInsertId()
|
||||
}
|
||||
|
||||
// Ingredient operations
|
||||
func GetUserByEmail(email string) (*models.User, error) {
|
||||
var user models.User
|
||||
err := DB.QueryRow(
|
||||
"SELECT id, email, password_hash, created_at FROM users WHERE email = ?",
|
||||
email,
|
||||
).Scan(&user.ID, &user.Email, &user.PasswordHash, &user.CreatedAt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
func GetAllIngredients() ([]models.Ingredient, error) {
|
||||
rows, err := DB.Query("SELECT id, name, unit FROM ingredients ORDER BY name")
|
||||
func GetUserByID(userID int) (*models.User, error) {
|
||||
var user models.User
|
||||
err := DB.QueryRow(
|
||||
"SELECT id, email, password_hash, created_at FROM users WHERE id = ?",
|
||||
userID,
|
||||
).Scan(&user.ID, &user.Email, &user.PasswordHash, &user.CreatedAt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
// Session operations
|
||||
|
||||
func CreateSession(token string, userID int, expiresAt time.Time) error {
|
||||
_, err := DB.Exec(
|
||||
"INSERT INTO sessions (token, user_id, expires_at) VALUES (?, ?, ?)",
|
||||
token, userID, expiresAt,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
func GetSession(token string) (*models.Session, error) {
|
||||
var session models.Session
|
||||
err := DB.QueryRow(
|
||||
"SELECT token, user_id, expires_at, created_at FROM sessions WHERE token = ? AND expires_at > datetime('now')",
|
||||
token,
|
||||
).Scan(&session.Token, &session.UserID, &session.ExpiresAt, &session.CreatedAt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &session, nil
|
||||
}
|
||||
|
||||
func DeleteSession(token string) error {
|
||||
_, err := DB.Exec("DELETE FROM sessions WHERE token = ?", token)
|
||||
return err
|
||||
}
|
||||
|
||||
func DeleteExpiredSessions() error {
|
||||
_, err := DB.Exec("DELETE FROM sessions WHERE expires_at < datetime('now')")
|
||||
return err
|
||||
}
|
||||
|
||||
// Ingredient operations (user-isolated)
|
||||
|
||||
func GetAllIngredients(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
|
||||
}
|
||||
@@ -104,7 +189,7 @@ func GetAllIngredients() ([]models.Ingredient, error) {
|
||||
var ingredients []models.Ingredient
|
||||
for rows.Next() {
|
||||
var ing models.Ingredient
|
||||
if err := rows.Scan(&ing.ID, &ing.Name, &ing.Unit); err != nil {
|
||||
if err := rows.Scan(&ing.ID, &ing.UserID, &ing.Name, &ing.Unit); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ingredients = append(ingredients, ing)
|
||||
@@ -112,23 +197,32 @@ func GetAllIngredients() ([]models.Ingredient, error) {
|
||||
return ingredients, nil
|
||||
}
|
||||
|
||||
func AddIngredient(name, unit string) (int64, error) {
|
||||
result, err := DB.Exec("INSERT INTO ingredients (name, unit) VALUES (?, ?)", name, unit)
|
||||
func AddIngredient(userID int, name, unit string) (int64, error) {
|
||||
result, err := DB.Exec(
|
||||
"INSERT INTO ingredients (user_id, name, unit) VALUES (?, ?, ?)",
|
||||
userID, name, unit,
|
||||
)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return result.LastInsertId()
|
||||
}
|
||||
|
||||
func DeleteIngredient(id int) error {
|
||||
_, err := DB.Exec("DELETE FROM ingredients WHERE id = ?", id)
|
||||
func DeleteIngredient(userID, ingredientID int) error {
|
||||
_, err := DB.Exec(
|
||||
"DELETE FROM ingredients WHERE id = ? AND user_id = ?",
|
||||
ingredientID, userID,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
// Meal operations
|
||||
// Meal operations (user-isolated)
|
||||
|
||||
func GetAllMeals() ([]models.Meal, error) {
|
||||
rows, err := DB.Query("SELECT id, name, description, meal_type FROM meals ORDER BY name")
|
||||
func GetAllMeals(userID int) ([]models.Meal, error) {
|
||||
rows, err := DB.Query(
|
||||
"SELECT id, user_id, name, description, meal_type FROM meals WHERE user_id = ? ORDER BY name",
|
||||
userID,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -137,7 +231,7 @@ func GetAllMeals() ([]models.Meal, error) {
|
||||
var meals []models.Meal
|
||||
for rows.Next() {
|
||||
var meal models.Meal
|
||||
if err := rows.Scan(&meal.ID, &meal.Name, &meal.Description, &meal.MealType); err != nil {
|
||||
if err := rows.Scan(&meal.ID, &meal.UserID, &meal.Name, &meal.Description, &meal.MealType); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
meals = append(meals, meal)
|
||||
@@ -145,40 +239,49 @@ func GetAllMeals() ([]models.Meal, error) {
|
||||
return meals, nil
|
||||
}
|
||||
|
||||
func GetMealByID(id int) (*models.Meal, error) {
|
||||
func GetMealByID(userID, mealID int) (*models.Meal, error) {
|
||||
var meal models.Meal
|
||||
err := DB.QueryRow("SELECT id, name, description, meal_type FROM meals WHERE id = ?", id).
|
||||
Scan(&meal.ID, &meal.Name, &meal.Description, &meal.MealType)
|
||||
err := DB.QueryRow(
|
||||
"SELECT id, user_id, name, description, meal_type FROM meals WHERE id = ? AND user_id = ?",
|
||||
mealID, userID,
|
||||
).Scan(&meal.ID, &meal.UserID, &meal.Name, &meal.Description, &meal.MealType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &meal, nil
|
||||
}
|
||||
|
||||
func AddMeal(name, description, mealType string) (int64, error) {
|
||||
result, err := DB.Exec("INSERT INTO meals (name, description, meal_type) VALUES (?, ?, ?)", name, description, mealType)
|
||||
func AddMeal(userID int, name, description, mealType string) (int64, error) {
|
||||
result, err := DB.Exec(
|
||||
"INSERT INTO meals (user_id, name, description, meal_type) VALUES (?, ?, ?, ?)",
|
||||
userID, name, description, mealType,
|
||||
)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return result.LastInsertId()
|
||||
}
|
||||
|
||||
func DeleteMeal(id int) error {
|
||||
_, err := DB.Exec("DELETE FROM meals WHERE id = ?", id)
|
||||
func DeleteMeal(userID, mealID int) error {
|
||||
_, err := DB.Exec(
|
||||
"DELETE FROM meals WHERE id = ? AND user_id = ?",
|
||||
mealID, userID,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
// Meal Ingredients operations
|
||||
|
||||
func GetMealIngredients(mealID int) ([]models.MealIngredient, error) {
|
||||
func GetMealIngredients(userID, mealID int) ([]models.MealIngredient, error) {
|
||||
query := `
|
||||
SELECT mi.meal_id, mi.ingredient_id, mi.quantity, i.name, i.unit
|
||||
FROM meal_ingredients mi
|
||||
JOIN ingredients i ON mi.ingredient_id = i.id
|
||||
WHERE mi.meal_id = ?
|
||||
JOIN meals m ON mi.meal_id = m.id
|
||||
WHERE mi.meal_id = ? AND m.user_id = ? AND i.user_id = ?
|
||||
ORDER BY i.name
|
||||
`
|
||||
rows, err := DB.Query(query, mealID)
|
||||
rows, err := DB.Query(query, mealID, userID, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -195,29 +298,53 @@ func GetMealIngredients(mealID int) ([]models.MealIngredient, error) {
|
||||
return ingredients, nil
|
||||
}
|
||||
|
||||
func AddMealIngredient(mealID, ingredientID int, quantity float64) error {
|
||||
_, err := DB.Exec(
|
||||
func AddMealIngredient(userID, mealID, ingredientID int, quantity float64) error {
|
||||
// Verify meal and ingredient belong to user
|
||||
var count int
|
||||
err := DB.QueryRow(
|
||||
"SELECT COUNT(*) FROM meals m, ingredients i WHERE m.id = ? AND i.id = ? AND m.user_id = ? AND i.user_id = ?",
|
||||
mealID, ingredientID, userID, userID,
|
||||
).Scan(&count)
|
||||
if err != nil || count == 0 {
|
||||
return fmt.Errorf("meal or ingredient not found or doesn't belong to user")
|
||||
}
|
||||
|
||||
_, err = DB.Exec(
|
||||
"INSERT OR REPLACE INTO meal_ingredients (meal_id, ingredient_id, quantity) VALUES (?, ?, ?)",
|
||||
mealID, ingredientID, quantity,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
func DeleteMealIngredient(mealID, ingredientID int) error {
|
||||
_, err := DB.Exec("DELETE FROM meal_ingredients WHERE meal_id = ? AND ingredient_id = ?", mealID, ingredientID)
|
||||
func DeleteMealIngredient(userID, mealID, ingredientID int) error {
|
||||
// Verify ownership
|
||||
var count int
|
||||
err := DB.QueryRow(
|
||||
"SELECT COUNT(*) FROM meals WHERE id = ? AND user_id = ?",
|
||||
mealID, userID,
|
||||
).Scan(&count)
|
||||
if err != nil || count == 0 {
|
||||
return fmt.Errorf("meal not found or doesn't belong to user")
|
||||
}
|
||||
|
||||
_, err = DB.Exec(
|
||||
"DELETE FROM meal_ingredients WHERE meal_id = ? AND ingredient_id = ?",
|
||||
mealID, ingredientID,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
// Week Plan operations
|
||||
// Week Plan operations (user-isolated)
|
||||
|
||||
func GetWeekPlan() ([]models.WeekPlanEntry, error) {
|
||||
func GetWeekPlan(userID int) ([]models.WeekPlanEntry, error) {
|
||||
query := `
|
||||
SELECT wp.id, wp.date, wp.meal_id, m.name, m.meal_type
|
||||
FROM week_plan wp
|
||||
JOIN meals m ON wp.meal_id = m.id
|
||||
WHERE wp.user_id = ?
|
||||
ORDER BY wp.date, m.meal_type
|
||||
`
|
||||
rows, err := DB.Query(query)
|
||||
rows, err := DB.Query(query, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -236,29 +363,47 @@ func GetWeekPlan() ([]models.WeekPlanEntry, error) {
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
func AddWeekPlanEntry(date time.Time, mealID int) error {
|
||||
func AddWeekPlanEntry(userID int, date time.Time, mealID int) error {
|
||||
// Verify meal belongs to user
|
||||
var count int
|
||||
err := DB.QueryRow(
|
||||
"SELECT COUNT(*) FROM meals WHERE id = ? AND user_id = ?",
|
||||
mealID, userID,
|
||||
).Scan(&count)
|
||||
if err != nil || count == 0 {
|
||||
return fmt.Errorf("meal not found or doesn't belong to user")
|
||||
}
|
||||
|
||||
dateStr := date.Format("2006-01-02")
|
||||
_, err := DB.Exec("INSERT INTO week_plan (date, meal_id) VALUES (?, ?)", dateStr, mealID)
|
||||
_, err = DB.Exec(
|
||||
"INSERT INTO week_plan (user_id, date, meal_id) VALUES (?, ?, ?)",
|
||||
userID, dateStr, mealID,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
func DeleteWeekPlanEntry(id int) error {
|
||||
_, err := DB.Exec("DELETE FROM week_plan WHERE id = ?", id)
|
||||
func DeleteWeekPlanEntry(userID, entryID int) error {
|
||||
_, err := DB.Exec(
|
||||
"DELETE FROM week_plan WHERE id = ? AND user_id = ?",
|
||||
entryID, userID,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
// Grocery List operations
|
||||
// Grocery List operations (user-isolated)
|
||||
|
||||
func GetGroceryList() ([]models.GroceryItem, error) {
|
||||
func GetGroceryList(userID int) ([]models.GroceryItem, error) {
|
||||
query := `
|
||||
SELECT i.name, SUM(mi.quantity) as total_quantity, i.unit
|
||||
FROM week_plan wp
|
||||
JOIN meal_ingredients mi ON wp.meal_id = mi.meal_id
|
||||
JOIN meals m ON wp.meal_id = m.id
|
||||
JOIN meal_ingredients mi ON m.id = mi.meal_id
|
||||
JOIN ingredients i ON mi.ingredient_id = i.id
|
||||
WHERE wp.user_id = ? AND m.user_id = ? AND i.user_id = ?
|
||||
GROUP BY i.id, i.name, i.unit
|
||||
ORDER BY i.name
|
||||
`
|
||||
rows, err := DB.Query(query)
|
||||
rows, err := DB.Query(query, userID, userID, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user