PRAGMA foreign_keys = ON; CREATE TABLE IF NOT EXISTS app_meta ( key TEXT PRIMARY KEY, value TEXT, updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS households ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, shopping_weekday INTEGER NOT NULL DEFAULT 5, shopping_prep_days INTEGER NOT NULL DEFAULT 1, shopping_reminder_time TEXT NOT NULL DEFAULT '18:00', created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, household_id INTEGER, username TEXT NOT NULL UNIQUE, email TEXT, display_name TEXT, role TEXT NOT NULL DEFAULT 'member', is_active INTEGER NOT NULL DEFAULT 1, password_hash TEXT NOT NULL, created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (household_id) REFERENCES households(id) ON DELETE RESTRICT ); CREATE UNIQUE INDEX IF NOT EXISTS idx_users_email_unique ON users (email) WHERE email IS NOT NULL AND email != ''; CREATE TABLE IF NOT EXISTS household_categories ( id INTEGER PRIMARY KEY AUTOINCREMENT, household_id INTEGER NOT NULL, name TEXT NOT NULL, builder_key TEXT NOT NULL DEFAULT 'neutral', sort_order INTEGER NOT NULL DEFAULT 100, is_active INTEGER NOT NULL DEFAULT 1, created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE (household_id, name), FOREIGN KEY (household_id) REFERENCES households(id) ON DELETE CASCADE ); CREATE TABLE IF NOT EXISTS user_settings ( user_id INTEGER PRIMARY KEY, reminders_enabled INTEGER NOT NULL DEFAULT 1, push_enabled INTEGER NOT NULL DEFAULT 0, notification_channel TEXT NOT NULL DEFAULT 'in_app', suggestion_style TEXT NOT NULL DEFAULT 'balanced', energy_preference TEXT NOT NULL DEFAULT 'neutral', protein_preference TEXT NOT NULL DEFAULT 'mixed', remind_before_shopping INTEGER NOT NULL DEFAULT 1, remind_on_shopping_day INTEGER NOT NULL DEFAULT 1, show_missing_for_upcoming_week INTEGER NOT NULL DEFAULT 1, show_planned_not_shopped INTEGER NOT NULL DEFAULT 1, remind_tomorrow_if_sparse INTEGER NOT NULL DEFAULT 1, remind_week_if_sparse INTEGER NOT NULL DEFAULT 1, push_missing_breakfast INTEGER NOT NULL DEFAULT 0, push_missing_lunch INTEGER NOT NULL DEFAULT 0, push_missing_dinner INTEGER NOT NULL DEFAULT 0, push_small_snack INTEGER NOT NULL DEFAULT 0, suggest_home_for_today INTEGER NOT NULL DEFAULT 1, remind_small_snack INTEGER NOT NULL DEFAULT 0, remind_nuts INTEGER NOT NULL DEFAULT 0, show_meal_balancing INTEGER NOT NULL DEFAULT 1, suggest_templates INTEGER NOT NULL DEFAULT 1, suggest_patterns INTEGER NOT NULL DEFAULT 1, updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE ); CREATE TABLE IF NOT EXISTS push_subscriptions ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, endpoint TEXT NOT NULL UNIQUE, p256dh TEXT NOT NULL, auth TEXT NOT NULL, user_agent TEXT, is_active INTEGER NOT NULL DEFAULT 1, created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, last_test_at TEXT, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE ); CREATE TABLE IF NOT EXISTS reminder_events ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, event_key TEXT NOT NULL, created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE (user_id, event_key), FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE ); CREATE TABLE IF NOT EXISTS hidden_generated_suggestions ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, suggestion_key TEXT NOT NULL, created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE (user_id, suggestion_key), FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE ); CREATE TABLE IF NOT EXISTS dayparts ( id INTEGER PRIMARY KEY AUTOINCREMENT, slug TEXT NOT NULL UNIQUE, name TEXT NOT NULL, sort_order INTEGER NOT NULL ); CREATE TABLE IF NOT EXISTS items ( id INTEGER PRIMARY KEY AUTOINCREMENT, household_id INTEGER, owner_user_id INTEGER, target_user_id INTEGER, visibility TEXT NOT NULL DEFAULT 'shared', kind TEXT NOT NULL CHECK (kind IN ('food', 'meal')), name TEXT NOT NULL, category TEXT, base_type TEXT NOT NULL DEFAULT 'neutral', flavor_profile TEXT NOT NULL DEFAULT 'neutral', suggestion_role TEXT NOT NULL DEFAULT 'base', suggestion_priority TEXT NOT NULL DEFAULT 'normal', can_be_meal_core INTEGER NOT NULL DEFAULT 0, meal_type TEXT, meal_tags TEXT NOT NULL DEFAULT '', energy_density TEXT NOT NULL DEFAULT 'neutral', note TEXT, photo_filename TEXT, availability_state TEXT NOT NULL DEFAULT 'idea' CHECK (availability_state IN ('idea', 'home', 'archived')), is_archived INTEGER NOT NULL DEFAULT 0, is_quick_added INTEGER NOT NULL DEFAULT 0, created_by INTEGER, updated_by INTEGER, created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (household_id) REFERENCES households(id) ON DELETE CASCADE, FOREIGN KEY (owner_user_id) REFERENCES users(id) ON DELETE SET NULL, FOREIGN KEY (target_user_id) REFERENCES users(id) ON DELETE SET NULL, FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE SET NULL, FOREIGN KEY (updated_by) REFERENCES users(id) ON DELETE SET NULL ); CREATE TABLE IF NOT EXISTS item_dayparts ( item_id INTEGER NOT NULL, daypart_id INTEGER NOT NULL, PRIMARY KEY (item_id, daypart_id), FOREIGN KEY (item_id) REFERENCES items(id) ON DELETE CASCADE, FOREIGN KEY (daypart_id) REFERENCES dayparts(id) ON DELETE CASCADE ); CREATE TABLE IF NOT EXISTS meal_components ( meal_item_id INTEGER NOT NULL, food_item_id INTEGER NOT NULL, PRIMARY KEY (meal_item_id, food_item_id), FOREIGN KEY (meal_item_id) REFERENCES items(id) ON DELETE CASCADE, FOREIGN KEY (food_item_id) REFERENCES items(id) ON DELETE CASCADE ); CREATE TABLE IF NOT EXISTS shopping_entries ( id INTEGER PRIMARY KEY AUTOINCREMENT, household_id INTEGER, owner_user_id INTEGER, visibility TEXT NOT NULL DEFAULT 'shared', item_id INTEGER NOT NULL, shopping_note TEXT NOT NULL DEFAULT '', added_by INTEGER, checked_by INTEGER, needed_for_date TEXT, needed_for_daypart_id INTEGER, is_checked INTEGER NOT NULL DEFAULT 0, added_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, checked_at TEXT, FOREIGN KEY (household_id) REFERENCES households(id) ON DELETE CASCADE, FOREIGN KEY (owner_user_id) REFERENCES users(id) ON DELETE SET NULL, FOREIGN KEY (item_id) REFERENCES items(id) ON DELETE CASCADE, FOREIGN KEY (added_by) REFERENCES users(id) ON DELETE SET NULL, FOREIGN KEY (checked_by) REFERENCES users(id) ON DELETE SET NULL, FOREIGN KEY (needed_for_daypart_id) REFERENCES dayparts(id) ON DELETE SET NULL ); CREATE UNIQUE INDEX IF NOT EXISTS idx_shopping_entries_open_item ON shopping_entries (item_id, COALESCE(shopping_note, '')) WHERE is_checked = 0; CREATE TABLE IF NOT EXISTS shopping_needs ( id INTEGER PRIMARY KEY AUTOINCREMENT, household_id INTEGER NOT NULL, owner_user_id INTEGER, visibility TEXT NOT NULL DEFAULT 'shared', item_id INTEGER NOT NULL, source_item_id INTEGER, needed_for_date TEXT NOT NULL, needed_for_daypart_id INTEGER, activation_date TEXT NOT NULL, is_activated INTEGER NOT NULL DEFAULT 0, activated_at TEXT, created_by INTEGER, created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE (item_id, source_item_id, needed_for_date, needed_for_daypart_id, visibility), FOREIGN KEY (household_id) REFERENCES households(id) ON DELETE CASCADE, FOREIGN KEY (owner_user_id) REFERENCES users(id) ON DELETE SET NULL, FOREIGN KEY (item_id) REFERENCES items(id) ON DELETE CASCADE, FOREIGN KEY (source_item_id) REFERENCES items(id) ON DELETE SET NULL, FOREIGN KEY (needed_for_daypart_id) REFERENCES dayparts(id) ON DELETE SET NULL, FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE SET NULL ); CREATE TABLE IF NOT EXISTS plan_entries ( id INTEGER PRIMARY KEY AUTOINCREMENT, household_id INTEGER, owner_user_id INTEGER, visibility TEXT NOT NULL DEFAULT 'shared', plan_date TEXT NOT NULL, daypart_id INTEGER NOT NULL, item_id INTEGER NOT NULL, note TEXT, created_by INTEGER, created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (household_id) REFERENCES households(id) ON DELETE CASCADE, FOREIGN KEY (owner_user_id) REFERENCES users(id) ON DELETE SET NULL, FOREIGN KEY (daypart_id) REFERENCES dayparts(id) ON DELETE CASCADE, FOREIGN KEY (item_id) REFERENCES items(id) ON DELETE CASCADE, FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE SET NULL ); CREATE TABLE IF NOT EXISTS day_templates ( id INTEGER PRIMARY KEY AUTOINCREMENT, household_id INTEGER NOT NULL, owner_user_id INTEGER, visibility TEXT NOT NULL DEFAULT 'shared', name TEXT NOT NULL, description TEXT, last_used_at TEXT, created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (household_id) REFERENCES households(id) ON DELETE CASCADE, FOREIGN KEY (owner_user_id) REFERENCES users(id) ON DELETE SET NULL ); CREATE TABLE IF NOT EXISTS day_template_entries ( id INTEGER PRIMARY KEY AUTOINCREMENT, day_template_id INTEGER NOT NULL, daypart_id INTEGER NOT NULL, item_id INTEGER NOT NULL, sort_order INTEGER NOT NULL DEFAULT 100, FOREIGN KEY (day_template_id) REFERENCES day_templates(id) ON DELETE CASCADE, FOREIGN KEY (daypart_id) REFERENCES dayparts(id) ON DELETE CASCADE, FOREIGN KEY (item_id) REFERENCES items(id) ON DELETE CASCADE ); CREATE TABLE IF NOT EXISTS week_templates ( id INTEGER PRIMARY KEY AUTOINCREMENT, household_id INTEGER NOT NULL, owner_user_id INTEGER, visibility TEXT NOT NULL DEFAULT 'shared', name TEXT NOT NULL, description TEXT, last_used_at TEXT, created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (household_id) REFERENCES households(id) ON DELETE CASCADE, FOREIGN KEY (owner_user_id) REFERENCES users(id) ON DELETE SET NULL ); CREATE TABLE IF NOT EXISTS week_template_days ( id INTEGER PRIMARY KEY AUTOINCREMENT, week_template_id INTEGER NOT NULL, weekday_index INTEGER NOT NULL, day_template_id INTEGER NOT NULL, UNIQUE (week_template_id, weekday_index), FOREIGN KEY (week_template_id) REFERENCES week_templates(id) ON DELETE CASCADE, FOREIGN KEY (day_template_id) REFERENCES day_templates(id) ON DELETE CASCADE ); CREATE TABLE IF NOT EXISTS item_sets ( id INTEGER PRIMARY KEY AUTOINCREMENT, household_id INTEGER NOT NULL, owner_user_id INTEGER, visibility TEXT NOT NULL DEFAULT 'shared', name TEXT NOT NULL, description TEXT, last_used_at TEXT, created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (household_id) REFERENCES households(id) ON DELETE CASCADE, FOREIGN KEY (owner_user_id) REFERENCES users(id) ON DELETE SET NULL ); CREATE TABLE IF NOT EXISTS item_set_items ( id INTEGER PRIMARY KEY AUTOINCREMENT, item_set_id INTEGER NOT NULL, item_id INTEGER NOT NULL, sort_order INTEGER NOT NULL DEFAULT 100, UNIQUE (item_set_id, item_id), FOREIGN KEY (item_set_id) REFERENCES item_sets(id) ON DELETE CASCADE, FOREIGN KEY (item_id) REFERENCES items(id) ON DELETE CASCADE ); CREATE INDEX IF NOT EXISTS idx_items_kind_name ON items (kind, name); CREATE INDEX IF NOT EXISTS idx_items_household_visibility ON items (household_id, visibility, availability_state); CREATE INDEX IF NOT EXISTS idx_items_household_visibility_archived ON items (household_id, visibility, is_archived, availability_state); CREATE INDEX IF NOT EXISTS idx_items_target_user ON items (target_user_id); CREATE INDEX IF NOT EXISTS idx_item_dayparts_daypart_item ON item_dayparts (daypart_id, item_id); CREATE INDEX IF NOT EXISTS idx_plan_entries_plan_date_daypart ON plan_entries (plan_date, daypart_id); CREATE INDEX IF NOT EXISTS idx_plan_entries_household_visibility ON plan_entries (household_id, visibility, plan_date); CREATE INDEX IF NOT EXISTS idx_shopping_entries_household_visibility ON shopping_entries (household_id, visibility, is_checked); CREATE INDEX IF NOT EXISTS idx_shopping_needs_household_activation ON shopping_needs (household_id, activation_date, is_activated); CREATE INDEX IF NOT EXISTS idx_day_templates_household_visibility ON day_templates (household_id, visibility, name); CREATE INDEX IF NOT EXISTS idx_week_templates_household_visibility ON week_templates (household_id, visibility, name); CREATE INDEX IF NOT EXISTS idx_item_sets_household_visibility ON item_sets (household_id, visibility, name);