release nouri 0.5.0 shopping rhythm pwa and reminders

This commit is contained in:
2026-04-12 16:39:04 +02:00
parent d8b56e6b67
commit 96ab52e1ba
37 changed files with 2199 additions and 285 deletions
+117 -4
View File
@@ -8,7 +8,7 @@ from flask import Flask, current_app, g
from flask.cli import with_appcontext
from werkzeug.security import generate_password_hash
from .constants import DAYPARTS, DEFAULT_CATEGORIES
from .constants import DAYPARTS, DEFAULT_CATEGORIES, DEFAULT_CATEGORY_BUILDERS
def get_db() -> sqlite3.Connection:
@@ -53,6 +53,9 @@ def bootstrap_legacy_schema(database: sqlite3.Connection) -> None:
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
)
"""
@@ -64,6 +67,7 @@ def bootstrap_legacy_schema(database: sqlite3.Connection) -> None:
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,
@@ -72,6 +76,14 @@ def bootstrap_legacy_schema(database: sqlite3.Connection) -> None:
"""
)
if table_exists(database, "households"):
add_column_if_missing(database, "households", "shopping_weekday INTEGER NOT NULL DEFAULT 5")
add_column_if_missing(database, "households", "shopping_prep_days INTEGER NOT NULL DEFAULT 1")
add_column_if_missing(database, "households", "shopping_reminder_time TEXT NOT NULL DEFAULT '18:00'")
if table_exists(database, "household_categories"):
add_column_if_missing(database, "household_categories", "builder_key TEXT NOT NULL DEFAULT 'neutral'")
if table_exists(database, "users"):
add_column_if_missing(database, "users", "household_id INTEGER")
add_column_if_missing(database, "users", "email TEXT")
@@ -92,6 +104,82 @@ def bootstrap_legacy_schema(database: sqlite3.Connection) -> None:
add_column_if_missing(database, "shopping_entries", "needed_for_date TEXT")
add_column_if_missing(database, "shopping_entries", "needed_for_daypart_id INTEGER")
if table_exists(database, "shopping_needs"):
add_column_if_missing(database, "shopping_needs", "source_item_id INTEGER")
add_column_if_missing(database, "shopping_needs", "activation_date TEXT")
add_column_if_missing(database, "shopping_needs", "is_activated INTEGER NOT NULL DEFAULT 0")
add_column_if_missing(database, "shopping_needs", "activated_at TEXT")
database.execute(
"""
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',
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,
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
)
"""
)
database.execute(
"""
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
)
"""
)
database.execute(
"""
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
)
"""
)
if table_exists(database, "plan_entries"):
add_column_if_missing(database, "plan_entries", "household_id INTEGER")
add_column_if_missing(database, "plan_entries", "owner_user_id INTEGER")
@@ -126,10 +214,18 @@ def sync_default_categories(database: sqlite3.Connection) -> None:
for sort_order, name in enumerate(DEFAULT_CATEGORIES, start=10):
database.execute(
"""
INSERT OR IGNORE INTO household_categories (household_id, name, sort_order, is_active)
VALUES (?, ?, ?, 1)
INSERT OR IGNORE INTO household_categories (household_id, name, builder_key, sort_order, is_active)
VALUES (?, ?, ?, ?, 1)
""",
(household_id, name, sort_order),
(household_id, name, DEFAULT_CATEGORY_BUILDERS.get(name, "neutral"), sort_order),
)
database.execute(
"""
UPDATE household_categories
SET builder_key = COALESCE(NULLIF(builder_key, ''), ?)
WHERE household_id = ? AND name = ?
""",
(DEFAULT_CATEGORY_BUILDERS.get(name, "neutral"), household_id, name),
)
@@ -141,6 +237,11 @@ def ensure_schema_upgrades(database: sqlite3.Connection) -> None:
add_column_if_missing(database, "users", "updated_at TEXT")
default_household_id = ensure_default_household(database)
database.execute("UPDATE households SET shopping_weekday = COALESCE(shopping_weekday, 5)")
database.execute("UPDATE households SET shopping_prep_days = COALESCE(shopping_prep_days, 1)")
database.execute(
"UPDATE households SET shopping_reminder_time = COALESCE(NULLIF(shopping_reminder_time, ''), '18:00')"
)
database.execute(
"UPDATE users SET household_id = ? WHERE household_id IS NULL",
(default_household_id,),
@@ -204,6 +305,12 @@ def ensure_schema_upgrades(database: sqlite3.Connection) -> None:
database.execute("UPDATE plan_entries SET visibility = 'shared' WHERE visibility IS NULL OR visibility = ''")
sync_default_categories(database)
database.execute(
"""
INSERT OR IGNORE INTO user_settings (user_id)
SELECT id FROM users
"""
)
database.execute(
"""
@@ -236,6 +343,12 @@ def ensure_schema_upgrades(database: sqlite3.Connection) -> None:
ON shopping_entries (household_id, visibility, is_checked)
"""
)
database.execute(
"""
CREATE INDEX IF NOT EXISTS idx_shopping_needs_household_activation
ON shopping_needs (household_id, activation_date, is_activated)
"""
)
def apply_schema(database: sqlite3.Connection) -> None: