db to user_id
Some checks failed
Deploy to Web Server flyer-crawler.projectium.com / deploy (push) Failing after 40s

This commit is contained in:
2025-11-24 11:54:06 -08:00
parent 11049b3c6b
commit 1c08d2dab1
31 changed files with 868 additions and 854 deletions

View File

@@ -4,7 +4,7 @@
-- ============================================================================
-- 1. Users - This replaces the Supabase `auth.users` table.
CREATE TABLE IF NOT EXISTS public.users (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
email TEXT NOT NULL UNIQUE,
password_hash TEXT,
refresh_token TEXT,
@@ -23,8 +23,8 @@ CREATE INDEX IF NOT EXISTS idx_users_refresh_token ON public.users(refresh_token
-- 2. Log key user activities for analytics.
-- This needs to be created early as many triggers will insert into it.
CREATE TABLE IF NOT EXISTS public.activity_log (
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
user_id UUID REFERENCES public.users(id) ON DELETE SET NULL,
activity_log_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
user_id UUID REFERENCES public.users(user_id) ON DELETE SET NULL,
action TEXT NOT NULL,
display_text TEXT NOT NULL,
icon TEXT,
@@ -38,32 +38,31 @@ CREATE INDEX IF NOT EXISTS idx_activity_log_user_id ON public.activity_log(user_
-- 3. for public user profiles.
-- This table is linked to the users table and stores non-sensitive user data.
CREATE TABLE IF NOT EXISTS public.profiles (
id UUID PRIMARY KEY REFERENCES public.users(id) ON DELETE CASCADE,
user_id UUID PRIMARY KEY REFERENCES public.users(user_id) ON DELETE CASCADE,
full_name TEXT,
avatar_url TEXT,
preferences JSONB,
role TEXT CHECK (role IN ('admin', 'user')),
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
updated_at TIMESTAMPTZ DEFAULT now() NOT NULL,
created_by UUID REFERENCES public.users(id) ON DELETE SET NULL,
updated_by UUID REFERENCES public.users(id) ON DELETE SET NULL
created_by UUID REFERENCES public.users(user_id) ON DELETE SET NULL,
updated_by UUID REFERENCES public.users(user_id) ON DELETE SET NULL
);
COMMENT ON TABLE public.profiles IS 'Stores public-facing user data, linked to the public.users table.';
-- 4. The 'stores' table for normalized store data.
CREATE TABLE IF NOT EXISTS public.stores (
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
store_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
name TEXT NOT NULL UNIQUE,
logo_url TEXT,
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
updated_at TIMESTAMPTZ DEFAULT now() NOT NULL,
created_by UUID REFERENCES public.users(id) ON DELETE SET NULL
);
created_by UUID REFERENCES public.users(user_id) ON DELETE SET NULL
COMMENT ON TABLE public.stores IS 'Stores metadata for grocery store chains (e.g., Safeway, Kroger).';
-- 5. The 'categories' table for normalized category data.
CREATE TABLE IF NOT EXISTS public.categories (
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
category_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
name TEXT NOT NULL UNIQUE,
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
updated_at TIMESTAMPTZ DEFAULT now() NOT NULL
@@ -72,11 +71,11 @@ COMMENT ON TABLE public.categories IS 'Stores a predefined list of grocery item
-- 6. flyers' table
CREATE TABLE IF NOT EXISTS public.flyers (
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
flyer_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
file_name TEXT NOT NULL,
image_url TEXT NOT NULL,
checksum TEXT UNIQUE,
store_id BIGINT REFERENCES public.stores(id),
store_id BIGINT REFERENCES public.stores(store_id),
valid_from DATE,
valid_to DATE,
store_address TEXT,
@@ -95,23 +94,23 @@ COMMENT ON COLUMN public.flyers.store_address IS 'The physical store address if
-- 7. The 'master_grocery_items' table. This is the master dictionary.
CREATE TABLE IF NOT EXISTS public.master_grocery_items (
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
master_grocery_item_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
name TEXT NOT NULL UNIQUE,
category_id BIGINT REFERENCES public.categories(id),
category_id BIGINT REFERENCES public.categories(category_id),
is_allergen BOOLEAN DEFAULT false,
allergy_info JSONB,
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
updated_at TIMESTAMPTZ DEFAULT now() NOT NULL,
created_by UUID REFERENCES public.users(id) ON DELETE SET NULL
created_by UUID REFERENCES public.users(user_id) ON DELETE SET NULL
);
COMMENT ON TABLE public.master_grocery_items IS 'The master dictionary of canonical grocery items. Each item has a unique name and is linked to a category.';
CREATE INDEX IF NOT EXISTS idx_master_grocery_items_category_id ON public.master_grocery_items(category_id);
-- 8. The 'user_watched_items' table. This links to the master list.
CREATE TABLE IF NOT EXISTS public.user_watched_items (
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
user_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE,
master_item_id BIGINT NOT NULL REFERENCES public.master_grocery_items(id) ON DELETE CASCADE,
user_watched_item_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
user_id UUID NOT NULL REFERENCES public.users(user_id) ON DELETE CASCADE,
master_item_id BIGINT NOT NULL REFERENCES public.master_grocery_items(master_grocery_item_id) ON DELETE CASCADE,
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
updated_at TIMESTAMPTZ DEFAULT now() NOT NULL,
UNIQUE(user_id, master_item_id)
@@ -121,19 +120,19 @@ CREATE INDEX IF NOT EXISTS idx_user_watched_items_master_item_id ON public.user_
-- 9. The 'flyer_items' table. This stores individual items from flyers.
CREATE TABLE IF NOT EXISTS public.flyer_items (
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
flyer_id BIGINT REFERENCES public.flyers(id) ON DELETE CASCADE,
flyer_item_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
flyer_id BIGINT REFERENCES public.flyers(flyer_id) ON DELETE CASCADE,
item TEXT NOT NULL,
price_display TEXT NOT NULL,
price_in_cents INTEGER,
quantity_num NUMERIC,
quantity TEXT NOT NULL,
category_id BIGINT REFERENCES public.categories(id),
category_id BIGINT REFERENCES public.categories(category_id),
category_name TEXT,
unit_price JSONB,
view_count INTEGER DEFAULT 0 NOT NULL,
click_count INTEGER DEFAULT 0 NOT NULL,
master_item_id BIGINT REFERENCES public.master_grocery_items(id),
master_item_id BIGINT REFERENCES public.master_grocery_items(master_grocery_item_id),
product_id BIGINT,
created_at TIMESTAMPTZ DEFAULT now() NOT NULL
);
@@ -159,8 +158,8 @@ CREATE INDEX IF NOT EXISTS flyer_items_item_trgm_idx ON public.flyer_items USING
-- 10. For user-defined alerts on watched items.
CREATE TABLE IF NOT EXISTS public.user_alerts (
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
user_watched_item_id BIGINT NOT NULL REFERENCES public.user_watched_items(id) ON DELETE CASCADE,
user_alert_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
user_watched_item_id BIGINT NOT NULL REFERENCES public.user_watched_items(user_watched_item_id) ON DELETE CASCADE,
alert_type TEXT NOT NULL CHECK (alert_type IN ('PRICE_BELOW', 'PERCENT_OFF_AVERAGE')),
threshold_value NUMERIC NOT NULL,
is_active BOOLEAN DEFAULT true NOT NULL,
@@ -174,8 +173,8 @@ CREATE INDEX IF NOT EXISTS idx_user_alerts_user_watched_item_id ON public.user_a
-- 11. Store notifications for users.
CREATE TABLE IF NOT EXISTS public.notifications (
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
user_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE,
notification_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
user_id UUID NOT NULL REFERENCES public.users(user_id) ON DELETE CASCADE,
content TEXT NOT NULL,
link_url TEXT,
is_read BOOLEAN DEFAULT false NOT NULL,
@@ -189,8 +188,8 @@ CREATE INDEX IF NOT EXISTS idx_notifications_user_id ON public.notifications(use
-- 12. Store individual store locations with geographic data.
CREATE TABLE IF NOT EXISTS public.store_locations (
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
store_id BIGINT NOT NULL REFERENCES public.stores(id) ON DELETE CASCADE,
store_location_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
store_id BIGINT NOT NULL REFERENCES public.stores(store_id) ON DELETE CASCADE,
address TEXT NOT NULL,
city TEXT,
province_state TEXT,
@@ -208,10 +207,10 @@ CREATE INDEX IF NOT EXISTS store_locations_geo_idx ON public.store_locations USI
-- 13. For aggregated, historical price data for master items.
CREATE TABLE IF NOT EXISTS public.item_price_history (
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
master_item_id BIGINT NOT NULL REFERENCES public.master_grocery_items(id) ON DELETE CASCADE,
item_price_history_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
master_item_id BIGINT NOT NULL REFERENCES public.master_grocery_items(master_grocery_item_id) ON DELETE CASCADE,
summary_date DATE NOT NULL,
store_location_id BIGINT REFERENCES public.store_locations(id) ON DELETE CASCADE,
store_location_id BIGINT REFERENCES public.store_locations(store_location_id) ON DELETE CASCADE,
min_price_in_cents INTEGER,
max_price_in_cents INTEGER,
avg_price_in_cents INTEGER,
@@ -231,8 +230,8 @@ CREATE INDEX IF NOT EXISTS idx_item_price_history_store_location_id ON public.it
-- 14. Map various names to a single master grocery item.
CREATE TABLE IF NOT EXISTS public.master_item_aliases (
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
master_item_id BIGINT NOT NULL REFERENCES public.master_grocery_items(id) ON DELETE CASCADE,
master_item_alias_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
master_item_id BIGINT NOT NULL REFERENCES public.master_grocery_items(master_grocery_item_id) ON DELETE CASCADE,
alias TEXT NOT NULL UNIQUE,
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
updated_at TIMESTAMPTZ DEFAULT now() NOT NULL
@@ -243,8 +242,8 @@ CREATE INDEX IF NOT EXISTS idx_master_item_aliases_master_item_id ON public.mast
-- 15. For user shopping lists.
CREATE TABLE IF NOT EXISTS public.shopping_lists (
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
user_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE,
shopping_list_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
user_id UUID NOT NULL REFERENCES public.users(user_id) ON DELETE CASCADE,
name TEXT NOT NULL,
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
updated_at TIMESTAMPTZ DEFAULT now() NOT NULL
@@ -254,9 +253,9 @@ CREATE INDEX IF NOT EXISTS idx_shopping_lists_user_id ON public.shopping_lists(u
-- 16. For items in a user's shopping list.
CREATE TABLE IF NOT EXISTS public.shopping_list_items (
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
shopping_list_id BIGINT NOT NULL REFERENCES public.shopping_lists(id) ON DELETE CASCADE,
master_item_id BIGINT REFERENCES public.master_grocery_items(id),
shopping_list_item_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
shopping_list_id BIGINT NOT NULL REFERENCES public.shopping_lists(shopping_list_id) ON DELETE CASCADE,
master_item_id BIGINT REFERENCES public.master_grocery_items(master_grocery_item_id),
custom_item_name TEXT,
quantity NUMERIC DEFAULT 1 NOT NULL,
is_purchased BOOLEAN DEFAULT false NOT NULL,
@@ -273,10 +272,10 @@ CREATE INDEX IF NOT EXISTS idx_shopping_list_items_master_item_id ON public.shop
-- 17. Manage shared access to shopping lists.
CREATE TABLE IF NOT EXISTS public.shared_shopping_lists (
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
shopping_list_id BIGINT NOT NULL REFERENCES public.shopping_lists(id) ON DELETE CASCADE,
shared_by_user_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE,
shared_with_user_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE,
shared_shopping_list_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
shopping_list_id BIGINT NOT NULL REFERENCES public.shopping_lists(shopping_list_id) ON DELETE CASCADE,
shared_by_user_id UUID NOT NULL REFERENCES public.users(user_id) ON DELETE CASCADE,
shared_with_user_id UUID NOT NULL REFERENCES public.users(user_id) ON DELETE CASCADE,
permission_level TEXT NOT NULL CHECK (permission_level IN ('view', 'edit')),
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
updated_at TIMESTAMPTZ DEFAULT now() NOT NULL,
@@ -289,8 +288,8 @@ CREATE INDEX IF NOT EXISTS idx_shared_shopping_lists_shared_with_user_id ON publ
-- 18. Store a user's collection of planned meals for a date range.
CREATE TABLE IF NOT EXISTS public.menu_plans (
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
user_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE,
menu_plan_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
user_id UUID NOT NULL REFERENCES public.users(user_id) ON DELETE CASCADE,
name TEXT NOT NULL,
start_date DATE NOT NULL,
end_date DATE NOT NULL,
@@ -303,10 +302,10 @@ CREATE INDEX IF NOT EXISTS idx_menu_plans_user_id ON public.menu_plans(user_id);
-- 19. Manage shared access to menu plans.
CREATE TABLE IF NOT EXISTS public.shared_menu_plans (
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
menu_plan_id BIGINT NOT NULL REFERENCES public.menu_plans(id) ON DELETE CASCADE,
shared_by_user_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE,
shared_with_user_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE,
shared_menu_plan_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
menu_plan_id BIGINT NOT NULL REFERENCES public.menu_plans(menu_plan_id) ON DELETE CASCADE,
shared_by_user_id UUID NOT NULL REFERENCES public.users(user_id) ON DELETE CASCADE,
shared_with_user_id UUID NOT NULL REFERENCES public.users(user_id) ON DELETE CASCADE,
permission_level TEXT NOT NULL CHECK (permission_level IN ('view', 'edit')),
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
updated_at TIMESTAMPTZ DEFAULT now() NOT NULL,
@@ -319,9 +318,9 @@ CREATE INDEX IF NOT EXISTS idx_shared_menu_plans_shared_with_user_id ON public.s
-- 20. Store user-submitted corrections for flyer items.
CREATE TABLE IF NOT EXISTS public.suggested_corrections (
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
flyer_item_id BIGINT NOT NULL REFERENCES public.flyer_items(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES public.users(id),
suggested_correction_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
flyer_item_id BIGINT NOT NULL REFERENCES public.flyer_items(flyer_item_id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES public.users(user_id),
correction_type TEXT NOT NULL,
suggested_value TEXT NOT NULL,
status TEXT DEFAULT 'pending' NOT NULL,
@@ -339,10 +338,10 @@ CREATE INDEX IF NOT EXISTS idx_suggested_corrections_user_id ON public.suggested
-- 21. For prices submitted directly by users from in-store.
CREATE TABLE IF NOT EXISTS public.user_submitted_prices (
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
user_id UUID NOT NULL REFERENCES public.users(id),
master_item_id BIGINT NOT NULL REFERENCES public.master_grocery_items(id),
store_id BIGINT NOT NULL REFERENCES public.stores(id),
user_submitted_price_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
user_id UUID NOT NULL REFERENCES public.users(user_id),
master_item_id BIGINT NOT NULL REFERENCES public.master_grocery_items(master_grocery_item_id),
store_id BIGINT NOT NULL REFERENCES public.stores(store_id),
price_in_cents INTEGER NOT NULL,
photo_url TEXT,
upvotes INTEGER DEFAULT 0 NOT NULL,
@@ -358,8 +357,8 @@ CREATE INDEX IF NOT EXISTS idx_user_submitted_prices_master_item_id ON public.us
-- 22. Log flyer items that could not be automatically matched to a master item.
CREATE TABLE IF NOT EXISTS public.unmatched_flyer_items (
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
flyer_item_id BIGINT NOT NULL REFERENCES public.flyer_items(id) ON DELETE CASCADE,
unmatched_flyer_item_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
flyer_item_id BIGINT NOT NULL REFERENCES public.flyer_items(flyer_item_id) ON DELETE CASCADE,
status TEXT DEFAULT 'pending' NOT NULL CHECK (status IN ('pending', 'reviewed', 'ignored')),
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
reviewed_at TIMESTAMPTZ,
@@ -371,10 +370,10 @@ CREATE INDEX IF NOT EXISTS idx_unmatched_flyer_items_flyer_item_id ON public.unm
-- 23. Store brand information.
CREATE TABLE IF NOT EXISTS public.brands (
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
brand_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
name TEXT NOT NULL UNIQUE,
logo_url TEXT,
store_id BIGINT REFERENCES public.stores(id) ON DELETE SET NULL,
store_id BIGINT REFERENCES public.stores(store_id) ON DELETE SET NULL,
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
updated_at TIMESTAMPTZ DEFAULT now() NOT NULL
);
@@ -383,9 +382,9 @@ COMMENT ON COLUMN public.brands.store_id IS 'If this is a store-specific brand (
-- 24. For specific products, linking a master item with a brand and size.
CREATE TABLE IF NOT EXISTS public.products (
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
master_item_id BIGINT NOT NULL REFERENCES public.master_grocery_items(id),
brand_id BIGINT REFERENCES public.brands(id),
product_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
master_item_id BIGINT NOT NULL REFERENCES public.master_grocery_items(master_grocery_item_id),
brand_id BIGINT REFERENCES public.brands(brand_id),
name TEXT NOT NULL,
description TEXT,
size TEXT,
@@ -403,8 +402,8 @@ CREATE INDEX IF NOT EXISTS idx_products_brand_id ON public.products(brand_id);
-- 25. Linking table for when one flyer is valid for multiple locations.
CREATE TABLE IF NOT EXISTS public.flyer_locations (
flyer_id BIGINT NOT NULL REFERENCES public.flyers(id) ON DELETE CASCADE,
store_location_id BIGINT NOT NULL REFERENCES public.store_locations(id) ON DELETE CASCADE,
flyer_id BIGINT NOT NULL REFERENCES public.flyers(flyer_id) ON DELETE CASCADE,
store_location_id BIGINT NOT NULL REFERENCES public.store_locations(store_location_id) ON DELETE CASCADE,
PRIMARY KEY (flyer_id, store_location_id),
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
updated_at TIMESTAMPTZ DEFAULT now() NOT NULL
@@ -415,9 +414,9 @@ CREATE INDEX IF NOT EXISTS idx_flyer_locations_store_location_id ON public.flyer
-- 26. Store recipes, which can be user-created or pre-populated.
CREATE TABLE IF NOT EXISTS public.recipes (
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
user_id UUID REFERENCES public.users(id) ON DELETE CASCADE,
original_recipe_id BIGINT REFERENCES public.recipes(id) ON DELETE SET NULL,
recipe_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
user_id UUID REFERENCES public.users(user_id) ON DELETE CASCADE,
original_recipe_id BIGINT REFERENCES public.recipes(recipe_id) ON DELETE SET NULL,
name TEXT NOT NULL,
description TEXT,
instructions TEXT,
@@ -452,9 +451,9 @@ CREATE UNIQUE INDEX IF NOT EXISTS idx_recipes_unique_system_recipe_name ON publi
-- 27. For ingredients required for each recipe.
CREATE TABLE IF NOT EXISTS public.recipe_ingredients (
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
recipe_id BIGINT NOT NULL REFERENCES public.recipes(id) ON DELETE CASCADE,
master_item_id BIGINT NOT NULL REFERENCES public.master_grocery_items(id),
recipe_ingredient_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
recipe_id BIGINT NOT NULL REFERENCES public.recipes(recipe_id) ON DELETE CASCADE,
master_item_id BIGINT NOT NULL REFERENCES public.master_grocery_items(master_grocery_item_id),
quantity NUMERIC NOT NULL,
unit TEXT NOT NULL,
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
@@ -467,9 +466,9 @@ CREATE INDEX IF NOT EXISTS idx_recipe_ingredients_master_item_id ON public.recip
-- 28. Suggest ingredient substitutions for a recipe.
CREATE TABLE IF NOT EXISTS public.recipe_ingredient_substitutions (
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
recipe_ingredient_id BIGINT NOT NULL REFERENCES public.recipe_ingredients(id) ON DELETE CASCADE,
substitute_master_item_id BIGINT NOT NULL REFERENCES public.master_grocery_items(id) ON DELETE CASCADE,
recipe_ingredient_substitution_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
recipe_ingredient_id BIGINT NOT NULL REFERENCES public.recipe_ingredients(recipe_ingredient_id) ON DELETE CASCADE,
substitute_master_item_id BIGINT NOT NULL REFERENCES public.master_grocery_items(master_grocery_item_id) ON DELETE CASCADE,
notes TEXT,
UNIQUE(recipe_ingredient_id, substitute_master_item_id),
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
@@ -481,7 +480,7 @@ CREATE INDEX IF NOT EXISTS idx_recipe_ingredient_substitutions_substitute_master
-- 29. Store a predefined list of tags for recipes.
CREATE TABLE IF NOT EXISTS public.tags (
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
tag_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
name TEXT NOT NULL UNIQUE,
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
updated_at TIMESTAMPTZ DEFAULT now() NOT NULL
@@ -490,8 +489,8 @@ COMMENT ON TABLE public.tags IS 'Stores tags for categorizing recipes, e.g., "Ve
-- 30. Associate multiple tags with a recipe.
CREATE TABLE IF NOT EXISTS public.recipe_tags (
recipe_id BIGINT NOT NULL REFERENCES public.recipes(id) ON DELETE CASCADE,
tag_id BIGINT NOT NULL REFERENCES public.tags(id) ON DELETE CASCADE,
recipe_id BIGINT NOT NULL REFERENCES public.recipes(recipe_id) ON DELETE CASCADE,
tag_id BIGINT NOT NULL REFERENCES public.tags(tag_id) ON DELETE CASCADE,
PRIMARY KEY (recipe_id, tag_id),
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
updated_at TIMESTAMPTZ DEFAULT now() NOT NULL
@@ -502,7 +501,7 @@ CREATE INDEX IF NOT EXISTS idx_recipe_tags_tag_id ON public.recipe_tags(tag_id);
-- 31. Store a predefined list of kitchen appliances.
CREATE TABLE IF NOT EXISTS public.appliances (
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
appliance_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
name TEXT NOT NULL UNIQUE,
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
updated_at TIMESTAMPTZ DEFAULT now() NOT NULL
@@ -511,8 +510,8 @@ COMMENT ON TABLE public.appliances IS 'A predefined list of kitchen appliances (
-- 32. Associate recipes with required appliances.
CREATE TABLE IF NOT EXISTS public.recipe_appliances (
recipe_id BIGINT NOT NULL REFERENCES public.recipes(id) ON DELETE CASCADE,
appliance_id BIGINT NOT NULL REFERENCES public.appliances(id) ON DELETE CASCADE,
recipe_id BIGINT NOT NULL REFERENCES public.recipes(recipe_id) ON DELETE CASCADE,
appliance_id BIGINT NOT NULL REFERENCES public.appliances(appliance_id) ON DELETE CASCADE,
PRIMARY KEY (recipe_id, appliance_id),
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
updated_at TIMESTAMPTZ DEFAULT now() NOT NULL
@@ -523,9 +522,9 @@ CREATE INDEX IF NOT EXISTS idx_recipe_appliances_appliance_id ON public.recipe_a
-- 33. Store individual user ratings for recipes.
CREATE TABLE IF NOT EXISTS public.recipe_ratings (
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
recipe_id BIGINT NOT NULL REFERENCES public.recipes(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE,
recipe_rating_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
recipe_id BIGINT NOT NULL REFERENCES public.recipes(recipe_id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES public.users(user_id) ON DELETE CASCADE,
rating INTEGER NOT NULL CHECK (rating >= 1 AND rating <= 5),
comment TEXT,
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
@@ -538,10 +537,10 @@ CREATE INDEX IF NOT EXISTS idx_recipe_ratings_user_id ON public.recipe_ratings(u
-- 34. For user comments on recipes to enable discussion.
CREATE TABLE IF NOT EXISTS public.recipe_comments (
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
recipe_id BIGINT NOT NULL REFERENCES public.recipes(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE,
parent_comment_id BIGINT REFERENCES public.recipe_comments(id) ON DELETE CASCADE,
recipe_comment_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
recipe_id BIGINT NOT NULL REFERENCES public.recipes(recipe_id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES public.users(user_id) ON DELETE CASCADE,
parent_comment_id BIGINT REFERENCES public.recipe_comments(recipe_comment_id) ON DELETE CASCADE,
content TEXT NOT NULL,
status TEXT DEFAULT 'visible' NOT NULL CHECK (status IN ('visible', 'hidden', 'reported')),
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
@@ -555,8 +554,8 @@ CREATE INDEX IF NOT EXISTS idx_recipe_comments_parent_comment_id ON public.recip
-- 35. For users to define locations within their pantry.
CREATE TABLE IF NOT EXISTS public.pantry_locations (
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
user_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE,
pantry_location_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
user_id UUID NOT NULL REFERENCES public.users(user_id) ON DELETE CASCADE,
name TEXT NOT NULL,
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
updated_at TIMESTAMPTZ DEFAULT now() NOT NULL,
@@ -567,9 +566,9 @@ CREATE INDEX IF NOT EXISTS idx_pantry_locations_user_id ON public.pantry_locatio
-- 36. Associate a recipe with a specific date and meal type within a menu plan.
CREATE TABLE IF NOT EXISTS public.planned_meals (
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
menu_plan_id BIGINT NOT NULL REFERENCES public.menu_plans(id) ON DELETE CASCADE,
recipe_id BIGINT NOT NULL REFERENCES public.recipes(id) ON DELETE CASCADE,
planned_meal_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
menu_plan_id BIGINT NOT NULL REFERENCES public.menu_plans(menu_plan_id) ON DELETE CASCADE,
recipe_id BIGINT NOT NULL REFERENCES public.recipes(recipe_id) ON DELETE CASCADE,
plan_date DATE NOT NULL,
meal_type TEXT NOT NULL,
servings_to_cook INTEGER,
@@ -583,13 +582,13 @@ CREATE INDEX IF NOT EXISTS idx_planned_meals_recipe_id ON public.planned_meals(r
-- 37. Track the grocery items a user currently has in their pantry.
CREATE TABLE IF NOT EXISTS public.pantry_items (
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
user_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE,
master_item_id BIGINT NOT NULL REFERENCES public.master_grocery_items(id) ON DELETE CASCADE,
pantry_item_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
user_id UUID NOT NULL REFERENCES public.users(user_id) ON DELETE CASCADE,
master_item_id BIGINT NOT NULL REFERENCES public.master_grocery_items(master_grocery_item_id) ON DELETE CASCADE,
quantity NUMERIC NOT NULL,
unit TEXT,
best_before_date DATE,
pantry_location_id BIGINT REFERENCES public.pantry_locations(id) ON DELETE SET NULL,
pantry_location_id BIGINT REFERENCES public.pantry_locations(pantry_location_id) ON DELETE SET NULL,
notification_sent_at TIMESTAMPTZ,
updated_at TIMESTAMPTZ DEFAULT now() NOT NULL,
UNIQUE(user_id, master_item_id, unit)
@@ -604,8 +603,8 @@ CREATE INDEX IF NOT EXISTS idx_pantry_items_pantry_location_id ON public.pantry_
-- 38. Store password reset tokens.
CREATE TABLE IF NOT EXISTS public.password_reset_tokens (
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
user_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE,
password_reset_token_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
user_id UUID NOT NULL REFERENCES public.users(user_id) ON DELETE CASCADE,
token_hash TEXT NOT NULL UNIQUE,
expires_at TIMESTAMPTZ NOT NULL,
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
@@ -619,8 +618,8 @@ CREATE INDEX IF NOT EXISTS idx_password_reset_tokens_token_hash ON public.passwo
-- 39. Store unit conversion factors for specific master grocery items.
CREATE TABLE IF NOT EXISTS public.unit_conversions (
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
master_item_id BIGINT NOT NULL REFERENCES public.master_grocery_items(id) ON DELETE CASCADE,
unit_conversion_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
master_item_id BIGINT NOT NULL REFERENCES public.master_grocery_items(master_grocery_item_id) ON DELETE CASCADE,
from_unit TEXT NOT NULL,
to_unit TEXT NOT NULL,
factor NUMERIC NOT NULL,
@@ -634,9 +633,9 @@ CREATE INDEX IF NOT EXISTS idx_unit_conversions_master_item_id ON public.unit_co
-- 40. For users to create their own private aliases for items.
CREATE TABLE IF NOT EXISTS public.user_item_aliases (
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
user_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE,
master_item_id BIGINT NOT NULL REFERENCES public.master_grocery_items(id) ON DELETE CASCADE,
user_item_alias_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
user_id UUID NOT NULL REFERENCES public.users(user_id) ON DELETE CASCADE,
master_item_id BIGINT NOT NULL REFERENCES public.master_grocery_items(master_grocery_item_id) ON DELETE CASCADE,
alias TEXT NOT NULL,
UNIQUE(user_id, alias),
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
@@ -648,8 +647,8 @@ CREATE INDEX IF NOT EXISTS idx_user_item_aliases_master_item_id ON public.user_i
-- 41. For users to mark their favorite recipes.
CREATE TABLE IF NOT EXISTS public.favorite_recipes (
user_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE,
recipe_id BIGINT NOT NULL REFERENCES public.recipes(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES public.users(user_id) ON DELETE CASCADE,
recipe_id BIGINT NOT NULL REFERENCES public.recipes(recipe_id) ON DELETE CASCADE,
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
PRIMARY KEY (user_id, recipe_id),
updated_at TIMESTAMPTZ DEFAULT now() NOT NULL
@@ -660,8 +659,8 @@ CREATE INDEX IF NOT EXISTS idx_favorite_recipes_recipe_id ON public.favorite_rec
-- 42. For users to mark their favorite stores.
CREATE TABLE IF NOT EXISTS public.favorite_stores (
user_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE,
store_id BIGINT NOT NULL REFERENCES public.stores(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES public.users(user_id) ON DELETE CASCADE,
store_id BIGINT NOT NULL REFERENCES public.stores(store_id) ON DELETE CASCADE,
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
PRIMARY KEY (user_id, store_id),
updated_at TIMESTAMPTZ DEFAULT now() NOT NULL
@@ -672,8 +671,8 @@ CREATE INDEX IF NOT EXISTS idx_favorite_stores_store_id ON public.favorite_store
-- 43. For users to group recipes into collections.
CREATE TABLE IF NOT EXISTS public.recipe_collections (
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
user_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE,
recipe_collection_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
user_id UUID NOT NULL REFERENCES public.users(user_id) ON DELETE CASCADE,
name TEXT NOT NULL,
description TEXT,
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
@@ -684,8 +683,8 @@ CREATE INDEX IF NOT EXISTS idx_recipe_collections_user_id ON public.recipe_colle
-- 44. Associate recipes with a user's collection.
CREATE TABLE IF NOT EXISTS public.recipe_collection_items (
collection_id BIGINT NOT NULL REFERENCES public.recipe_collections(id) ON DELETE CASCADE,
recipe_id BIGINT NOT NULL REFERENCES public.recipes(id) ON DELETE CASCADE,
collection_id BIGINT NOT NULL REFERENCES public.recipe_collections(recipe_collection_id) ON DELETE CASCADE,
recipe_id BIGINT NOT NULL REFERENCES public.recipes(recipe_id) ON DELETE CASCADE,
added_at TIMESTAMPTZ DEFAULT now() NOT NULL,
PRIMARY KEY (collection_id, recipe_id),
updated_at TIMESTAMPTZ DEFAULT now() NOT NULL
@@ -696,8 +695,8 @@ CREATE INDEX IF NOT EXISTS idx_recipe_collection_items_recipe_id ON public.recip
-- 45. Log user search queries for analysis.
CREATE TABLE IF NOT EXISTS public.search_queries (
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
user_id UUID REFERENCES public.users(id) ON DELETE SET NULL,
search_query_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
user_id UUID REFERENCES public.users(user_id) ON DELETE SET NULL,
query_text TEXT NOT NULL,
result_count INTEGER,
was_successful BOOLEAN,
@@ -710,9 +709,9 @@ CREATE INDEX IF NOT EXISTS idx_search_queries_user_id ON public.search_queries(u
-- 46. Store historical records of completed shopping trips.
CREATE TABLE IF NOT EXISTS public.shopping_trips (
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
user_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE,
shopping_list_id BIGINT REFERENCES public.shopping_lists(id) ON DELETE SET NULL,
shopping_trip_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
user_id UUID NOT NULL REFERENCES public.users(user_id) ON DELETE CASCADE,
shopping_list_id BIGINT REFERENCES public.shopping_lists(shopping_list_id) ON DELETE SET NULL,
completed_at TIMESTAMPTZ DEFAULT now() NOT NULL,
total_spent_cents INTEGER,
updated_at TIMESTAMPTZ DEFAULT now() NOT NULL
@@ -724,9 +723,9 @@ CREATE INDEX IF NOT EXISTS idx_shopping_trips_shopping_list_id ON public.shoppin
-- 47. Store the items purchased during a specific shopping trip.
CREATE TABLE IF NOT EXISTS public.shopping_trip_items (
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
shopping_trip_id BIGINT NOT NULL REFERENCES public.shopping_trips(id) ON DELETE CASCADE,
master_item_id BIGINT REFERENCES public.master_grocery_items(id),
shopping_trip_item_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
shopping_trip_id BIGINT NOT NULL REFERENCES public.shopping_trips(shopping_trip_id) ON DELETE CASCADE,
master_item_id BIGINT REFERENCES public.master_grocery_items(master_grocery_item_id),
custom_item_name TEXT,
quantity NUMERIC NOT NULL,
price_paid_cents INTEGER,
@@ -741,7 +740,7 @@ CREATE INDEX IF NOT EXISTS idx_shopping_trip_items_master_item_id ON public.shop
-- 48. Store predefined dietary restrictions (diets and allergies).
CREATE TABLE IF NOT EXISTS public.dietary_restrictions (
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
dietary_restriction_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
name TEXT NOT NULL UNIQUE,
type TEXT NOT NULL CHECK (type IN ('diet', 'allergy')),
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
@@ -751,8 +750,8 @@ COMMENT ON TABLE public.dietary_restrictions IS 'A predefined list of common die
-- 49. For a user's specific dietary restrictions.
CREATE TABLE IF NOT EXISTS public.user_dietary_restrictions (
user_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE,
restriction_id BIGINT NOT NULL REFERENCES public.dietary_restrictions(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES public.users(user_id) ON DELETE CASCADE,
restriction_id BIGINT NOT NULL REFERENCES public.dietary_restrictions(dietary_restriction_id) ON DELETE CASCADE,
PRIMARY KEY (user_id, restriction_id),
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
updated_at TIMESTAMPTZ DEFAULT now() NOT NULL
@@ -763,8 +762,8 @@ CREATE INDEX IF NOT EXISTS idx_user_dietary_restrictions_restriction_id ON publi
-- 50. For a user's owned kitchen appliances.
CREATE TABLE IF NOT EXISTS public.user_appliances (
user_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE,
appliance_id BIGINT NOT NULL REFERENCES public.appliances(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES public.users(user_id) ON DELETE CASCADE,
appliance_id BIGINT NOT NULL REFERENCES public.appliances(appliance_id) ON DELETE CASCADE,
PRIMARY KEY (user_id, appliance_id),
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
updated_at TIMESTAMPTZ DEFAULT now() NOT NULL
@@ -775,8 +774,8 @@ CREATE INDEX IF NOT EXISTS idx_user_appliances_appliance_id ON public.user_appli
-- 51. Manage the social graph (following relationships).
CREATE TABLE IF NOT EXISTS public.user_follows (
follower_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE,
following_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE,
follower_id UUID NOT NULL REFERENCES public.users(user_id) ON DELETE CASCADE,
following_id UUID NOT NULL REFERENCES public.users(user_id) ON DELETE CASCADE,
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
PRIMARY KEY (follower_id, following_id),
updated_at TIMESTAMPTZ DEFAULT now() NOT NULL,
@@ -788,9 +787,9 @@ CREATE INDEX IF NOT EXISTS idx_user_follows_following_id ON public.user_follows(
-- 52. Store uploaded user receipts for purchase tracking and analysis.
CREATE TABLE IF NOT EXISTS public.receipts (
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
user_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE,
store_id BIGINT REFERENCES public.stores(id),
receipt_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
user_id UUID NOT NULL REFERENCES public.users(user_id) ON DELETE CASCADE,
store_id BIGINT REFERENCES public.stores(store_id),
receipt_image_url TEXT NOT NULL,
transaction_date TIMESTAMPTZ,
total_amount_cents INTEGER,
@@ -806,13 +805,13 @@ CREATE INDEX IF NOT EXISTS idx_receipts_store_id ON public.receipts(store_id);
-- 53. Store individual line items extracted from a user receipt.
CREATE TABLE IF NOT EXISTS public.receipt_items (
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
receipt_id BIGINT NOT NULL REFERENCES public.receipts(id) ON DELETE CASCADE,
receipt_item_id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
receipt_id BIGINT NOT NULL REFERENCES public.receipts(receipt_id) ON DELETE CASCADE,
raw_item_description TEXT NOT NULL,
quantity NUMERIC DEFAULT 1 NOT NULL,
price_paid_cents INTEGER NOT NULL,
master_item_id BIGINT REFERENCES public.master_grocery_items(id),
product_id BIGINT REFERENCES public.products(id),
master_item_id BIGINT REFERENCES public.master_grocery_items(master_grocery_item_id),
product_id BIGINT REFERENCES public.products(product_id),
status TEXT DEFAULT 'unmatched' NOT NULL CHECK (status IN ('unmatched', 'matched', 'needs_review', 'ignored')),
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
updated_at TIMESTAMPTZ DEFAULT now() NOT NULL