658 lines
35 KiB
SQL
658 lines
35 KiB
SQL
|
|
-- DONE
|
|
-- 0. Create a simple users table for future expansion.
|
|
-- 0. Create a table for public user profiles.
|
|
-- This table is linked to the auth.users table and stores non-sensitive user data.
|
|
CREATE TABLE IF NOT EXISTS public.profiles (
|
|
id UUID PRIMARY KEY REFERENCES auth.users ON DELETE CASCADE,
|
|
updated_at TIMESTAMPTZ,
|
|
username TEXT UNIQUE,
|
|
full_name TEXT,
|
|
avatar_url TEXT,
|
|
CONSTRAINT username_length CHECK (char_length(username) >= 3)
|
|
);
|
|
|
|
-- DONE
|
|
-- 1. Create the 'stores' table for normalized store data.
|
|
CREATE TABLE IF NOT EXISTS public.stores (
|
|
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
|
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
|
name TEXT NOT NULL UNIQUE,
|
|
logo_url TEXT
|
|
);
|
|
|
|
-- DONE
|
|
-- 2. Create the 'categories' table for normalized category data.
|
|
CREATE TABLE IF NOT EXISTS public.categories (
|
|
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
|
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
|
name TEXT NOT NULL UNIQUE
|
|
);
|
|
|
|
-- DONE
|
|
-- 3. Create the 'flyers' table with its full, final schema.
|
|
CREATE TABLE IF NOT EXISTS public.flyers (
|
|
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
|
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
|
file_name TEXT,
|
|
image_url TEXT,
|
|
checksum TEXT UNIQUE,
|
|
store_id BIGINT REFERENCES public.stores(id),
|
|
valid_from DATE,
|
|
valid_to DATE
|
|
);
|
|
|
|
-- DONE
|
|
-- 4. Create 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,
|
|
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
|
name TEXT NOT NULL UNIQUE,
|
|
category_id BIGINT REFERENCES public.categories(id)
|
|
);
|
|
|
|
-- DONE
|
|
-- 5. Create 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,
|
|
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
|
user_id UUID NOT NULL REFERENCES public.profiles(id) ON DELETE CASCADE,
|
|
master_item_id BIGINT NOT NULL REFERENCES public.master_grocery_items(id) ON DELETE CASCADE,
|
|
UNIQUE(user_id, master_item_id)
|
|
);
|
|
|
|
-- DONE
|
|
-- 6. Create the 'flyer_items' table with its full, final schema.
|
|
CREATE TABLE IF NOT EXISTS public.flyer_items (
|
|
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
|
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
|
item TEXT,
|
|
price_display TEXT,
|
|
price_in_cents INTEGER,
|
|
quantity TEXT,
|
|
quantity_num NUMERIC,
|
|
unit_price JSONB,
|
|
flyer_id BIGINT REFERENCES public.flyers(id) ON DELETE CASCADE,
|
|
master_item_id BIGINT REFERENCES public.master_grocery_items(id)
|
|
);
|
|
|
|
-- DONE
|
|
-- 7. Create a table 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,
|
|
alert_type TEXT NOT NULL, -- e.g., 'PRICE_BELOW', 'PERCENT_OFF_AVERAGE'
|
|
threshold_value NUMERIC NOT NULL, -- The value for the alert condition (e.g., 299 for a price of $2.99, or 20 for 20%)
|
|
is_active BOOLEAN DEFAULT true NOT NULL,
|
|
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
|
-- A user should only have one type of alert per watched item
|
|
UNIQUE(user_watched_item_id, alert_type)
|
|
);
|
|
COMMENT ON COLUMN public.user_alerts.alert_type IS 'The condition that triggers the alert, e.g., ''PRICE_BELOW''.';
|
|
COMMENT ON COLUMN public.user_alerts.threshold_value IS 'The numeric threshold for the alert condition (e.g., price in cents, or percentage).';
|
|
|
|
-- DONE
|
|
-- 8. Create a table to 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.profiles(id) ON DELETE CASCADE,
|
|
content TEXT NOT NULL, -- The message for the user, e.g., "Chicken Thighs are on sale at Superstore!"
|
|
link_url TEXT, -- A deep link to the relevant flyer or item in the app
|
|
is_read BOOLEAN DEFAULT false NOT NULL,
|
|
created_at TIMESTAMPTZ DEFAULT now() NOT NULL
|
|
);
|
|
COMMENT ON COLUMN public.notifications.content IS 'The notification message displayed to the user.';
|
|
COMMENT ON COLUMN public.notifications.link_url IS 'A URL to navigate to when the notification is clicked.';
|
|
|
|
-- DONE
|
|
-- 9. Create a table 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,
|
|
summary_date DATE NOT NULL, -- The date this summary applies to
|
|
min_price_in_cents INTEGER, -- The lowest price found for this item on this day, in cents
|
|
max_price_in_cents INTEGER, -- The highest price found for this item on this day, in cents
|
|
avg_price_in_cents INTEGER, -- The average price found for this item on this day, in cents
|
|
data_points_count INTEGER NOT NULL, -- How many data points were used for this summary
|
|
UNIQUE(master_item_id, summary_date)
|
|
);
|
|
COMMENT ON TABLE public.item_price_history IS 'Serves as a summary table to speed up charting and analytics.';
|
|
COMMENT ON COLUMN public.item_price_history.summary_date IS 'The date for which the price data is summarized.';
|
|
|
|
-- DONE
|
|
-- 10. Create a table to 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,
|
|
alias TEXT NOT NULL UNIQUE
|
|
);
|
|
COMMENT ON TABLE public.master_item_aliases IS 'Stores synonyms or alternative names for master items to improve matching.';
|
|
COMMENT ON COLUMN public.master_item_aliases.alias IS 'An alternative name, e.g., "Ground Chuck" for the master item "Ground Beef".';
|
|
|
|
-- DONE
|
|
-- 11. Create tables 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.profiles(id) ON DELETE CASCADE,
|
|
name TEXT NOT NULL,
|
|
created_at TIMESTAMPTZ DEFAULT now() NOT NULL
|
|
);
|
|
COMMENT ON TABLE public.shopping_lists IS 'Stores user-created shopping lists, e.g., "Weekly Groceries".';
|
|
|
|
-- DONE
|
|
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),
|
|
custom_item_name TEXT, -- For items not in the master list, e.g., "Grandma's special spice mix"
|
|
quantity INTEGER DEFAULT 1 NOT NULL,
|
|
is_purchased BOOLEAN DEFAULT false NOT NULL,
|
|
added_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
|
-- Ensure a master item is not added twice to the same list
|
|
UNIQUE(shopping_list_id, master_item_id),
|
|
-- Ensure one of the item identifiers is present
|
|
CONSTRAINT must_have_item_identifier CHECK (master_item_id IS NOT NULL OR custom_item_name IS NOT NULL)
|
|
);
|
|
COMMENT ON TABLE public.shopping_list_items IS 'Contains individual items for a specific shopping list.';
|
|
COMMENT ON COLUMN public.shopping_list_items.custom_item_name IS 'For items not in the master list, e.g., "Grandma''s special spice mix".';
|
|
COMMENT ON COLUMN public.shopping_list_items.is_purchased IS 'Lets users check items off their list as they shop.';
|
|
|
|
|
|
-- DONE
|
|
-- 12. Create a table to 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.profiles(id) ON DELETE CASCADE,
|
|
correction_type TEXT NOT NULL, -- e.g., 'WRONG_PRICE', 'INCORRECT_ITEM_LINK', 'INVALID_QUANTITY'
|
|
suggested_value TEXT NOT NULL, -- The proposed new value, stored as text for flexibility.
|
|
status TEXT DEFAULT 'pending' NOT NULL, -- e.g., 'pending', 'approved', 'rejected'
|
|
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
|
reviewed_notes TEXT, -- An admin can leave a note about why a suggestion was approved/rejected.
|
|
reviewed_at TIMESTAMPTZ
|
|
);
|
|
COMMENT ON TABLE public.suggested_corrections IS 'A queue for user-submitted data corrections, enabling crowdsourced data quality improvements.';
|
|
COMMENT ON COLUMN public.suggested_corrections.correction_type IS 'The type of error the user is reporting.';
|
|
COMMENT ON COLUMN public.suggested_corrections.suggested_value IS 'The corrected value proposed by the user (e.g., a new price or master_item_id).';
|
|
COMMENT ON COLUMN public.suggested_corrections.status IS 'The moderation status of the correction: pending, approved, or rejected.';
|
|
|
|
-- DONE
|
|
-- 13. Create a table 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.profiles(id) ON DELETE CASCADE,
|
|
master_item_id BIGINT NOT NULL REFERENCES public.master_grocery_items(id) ON DELETE CASCADE,
|
|
store_id BIGINT NOT NULL REFERENCES public.stores(id) ON DELETE CASCADE,
|
|
price_in_cents INTEGER NOT NULL,
|
|
photo_url TEXT, -- Optional: URL to a photo of the price tag for verification.
|
|
upvotes INTEGER DEFAULT 1 NOT NULL, -- Community validation mechanism.
|
|
downvotes INTEGER DEFAULT 0 NOT NULL,
|
|
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
|
-- A user can only submit one price for an item at a store per day.
|
|
UNIQUE(user_id, master_item_id, store_id, created_at::date)
|
|
);
|
|
COMMENT ON TABLE public.user_submitted_prices IS 'Stores item prices submitted by users directly from physical stores.';
|
|
COMMENT ON COLUMN public.user_submitted_prices.photo_url IS 'URL to user-submitted photo evidence of the price.';
|
|
COMMENT ON COLUMN public.user_submitted_prices.upvotes IS 'Community validation score indicating accuracy.';
|
|
|
|
-- 14. Pre-populate categories table from a predefined list.
|
|
INSERT INTO public.categories (name) VALUES
|
|
('Fruits & Vegetables'), ('Meat & Seafood'), ('Dairy & Eggs'), ('Bakery & Bread'),
|
|
('Pantry & Dry Goods'), ('Beverages'), ('Frozen Foods'), ('Snacks'), ('Household & Cleaning'),
|
|
('Personal Care & Health'), ('Baby & Child'), ('Pet Supplies'), ('Deli & Prepared Foods'),
|
|
('Canned Goods'), ('Condiments & Spices'), ('Breakfast & Cereal'), ('Organic'),
|
|
('International Foods'), ('Other/Miscellaneous')
|
|
ON CONFLICT (name) DO NOTHING;
|
|
|
|
-- DONE
|
|
-- A table to store brand information.
|
|
CREATE TABLE IF NOT EXISTS public.brands (
|
|
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
|
name TEXT NOT NULL UNIQUE
|
|
);
|
|
COMMENT ON TABLE public.brands IS 'Stores brand names like "Coca-Cola", "Maple Leaf", or "Kraft".';
|
|
|
|
-- DONE
|
|
-- A table 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), -- Can be null for generic/store-brand items
|
|
name TEXT NOT NULL, -- e.g., "Prime Raised without Antibiotics Chicken Breast"
|
|
description TEXT,
|
|
size TEXT, -- e.g., "4L", "500g"
|
|
upc_code TEXT UNIQUE, -- Universal Product Code for precise identification
|
|
UNIQUE(master_item_id, brand_id, name, size)
|
|
);
|
|
COMMENT ON TABLE public.products IS 'Represents a specific, sellable product, combining a generic item with a brand and size.';
|
|
COMMENT ON COLUMN public.products.upc_code IS 'Universal Product Code, if available, for exact product matching.';
|
|
|
|
-- Then, you would update 'flyer_items' to link to this new table.
|
|
ALTER TABLE public.flyer_items
|
|
ADD COLUMN IF NOT EXISTS product_id BIGINT REFERENCES public.products(id);
|
|
|
|
-- Enable trigram support for fuzzy string matching
|
|
CREATE EXTENSION IF NOT EXISTS pg_trgm;
|
|
-- Add a GIN index to the 'item' column for fast fuzzy text searching.
|
|
CREATE INDEX IF NOT EXISTS flyer_items_item_trgm_idx ON public.flyer_items USING GIN (item gin_trgm_ops);
|
|
|
|
-- First, enable the PostGIS extension if you haven't already.
|
|
-- In Supabase, you can do this under Database -> Extensions.
|
|
-- CREATE EXTENSION IF NOT EXISTS postgis;
|
|
|
|
-- DONE
|
|
-- A table to 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,
|
|
address TEXT NOT NULL,
|
|
city TEXT,
|
|
province_state TEXT,
|
|
postal_code TEXT,
|
|
-- Use the 'geography' type for lat/lon data.
|
|
location GEOGRAPHY(Point, 4326)
|
|
);
|
|
CREATE INDEX IF NOT EXISTS store_locations_geo_idx ON public.store_locations USING GIST (location);
|
|
COMMENT ON TABLE public.store_locations IS 'Stores physical locations of stores with geographic data for proximity searches.';
|
|
COMMENT ON COLUMN public.store_locations.location IS 'Geographic coordinates (longitude, latitude) of the store.';
|
|
|
|
-- DONE
|
|
-- You might also need a linking table if 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,
|
|
PRIMARY KEY (flyer_id, store_location_id)
|
|
);
|
|
|
|
-- done
|
|
-- A table to 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.profiles(id) ON DELETE SET NULL, -- Can be a system recipe (user_id is NULL) or user-submitted
|
|
name TEXT NOT NULL,
|
|
description TEXT,
|
|
instructions TEXT,
|
|
prep_time_minutes INTEGER,
|
|
cook_time_minutes INTEGER,
|
|
servings INTEGER, -- How many people the recipe is intended for.
|
|
photo_url TEXT,
|
|
-- Optional nutritional information
|
|
calories_per_serving INTEGER,
|
|
protein_grams INTEGER,
|
|
fat_grams INTEGER,
|
|
carb_grams INTEGER,
|
|
-- Aggregated rating data for fast sorting/display
|
|
avg_rating NUMERIC(3, 2) DEFAULT 0.00,
|
|
rating_count INTEGER DEFAULT 0,
|
|
created_at TIMESTAMPTZ DEFAULT now() NOT NULL
|
|
);
|
|
COMMENT ON TABLE public.recipes IS 'Stores recipes that can be used to generate shopping lists.';
|
|
COMMENT ON COLUMN public.recipes.servings IS 'The number of servings this recipe yields.';
|
|
|
|
-- done
|
|
-- A linking table 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),
|
|
quantity NUMERIC NOT NULL,
|
|
unit TEXT NOT NULL -- e.g., 'cups', 'tbsp', 'g', 'each'
|
|
);
|
|
COMMENT ON TABLE public.recipe_ingredients IS 'Defines the ingredients and quantities needed for a recipe.';
|
|
|
|
-- done
|
|
-- A table to store a predefined list of tags for recipes.
|
|
CREATE TABLE IF NOT EXISTS public.tags (
|
|
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
|
name TEXT NOT NULL UNIQUE
|
|
);
|
|
COMMENT ON TABLE public.tags IS 'Stores tags for categorizing recipes, e.g., "Vegetarian", "Quick & Easy".';
|
|
|
|
-- done
|
|
-- A linking table to 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,
|
|
PRIMARY KEY (recipe_id, tag_id)
|
|
);
|
|
|
|
-- done
|
|
-- A table to 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.profiles(id) ON DELETE CASCADE,
|
|
rating INTEGER NOT NULL CHECK (rating >= 1 AND rating <= 5),
|
|
comment TEXT,
|
|
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
|
UNIQUE(recipe_id, user_id) -- A user can only rate a recipe once.
|
|
);
|
|
|
|
|
|
-- DONE
|
|
-- A table to 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.profiles(id) ON DELETE CASCADE,
|
|
name TEXT NOT NULL,
|
|
start_date DATE NOT NULL,
|
|
end_date DATE NOT NULL,
|
|
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
|
CONSTRAINT date_range_check CHECK (end_date >= start_date)
|
|
);
|
|
COMMENT ON TABLE public.menu_plans IS 'Represents a user''s meal plan for a specific period, e.g., "Week of Oct 23".';
|
|
|
|
-- DONE
|
|
-- A table to 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,
|
|
plan_date DATE NOT NULL,
|
|
-- e.g., 'Breakfast', 'Lunch', 'Dinner', 'Snack'
|
|
meal_type TEXT NOT NULL,
|
|
-- A user can plan the same recipe for multiple meal types on the same day (e.g., leftovers for lunch)
|
|
);
|
|
COMMENT ON TABLE public.planned_meals IS 'Assigns a recipe to a specific day and meal type within a user''s menu plan.';
|
|
COMMENT ON COLUMN public.planned_meals.meal_type IS 'The designated meal for the recipe, e.g., ''Breakfast'', ''Lunch'', ''Dinner''.';
|
|
|
|
-- DONE
|
|
-- A table to 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.profiles(id) ON DELETE CASCADE,
|
|
master_item_id BIGINT NOT NULL REFERENCES public.master_grocery_items(id) ON DELETE CASCADE,
|
|
quantity NUMERIC NOT NULL,
|
|
unit TEXT, -- e.g., 'g', 'ml', 'items'. Should align with recipe_ingredients.unit
|
|
best_before_date DATE,
|
|
updated_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
|
-- A user should only have one entry per master item in their pantry.
|
|
UNIQUE(user_id, master_item_id)
|
|
);
|
|
COMMENT ON TABLE public.pantry_items IS 'Tracks a user''s personal inventory of grocery items to enable smart shopping lists.';
|
|
COMMENT ON COLUMN public.pantry_items.quantity IS 'The current amount of the item. Convention: use grams for weight, mL for volume where applicable.';
|
|
COMMENT ON COLUMN public.pantry_items.unit IS 'e.g., ''g'', ''ml'', ''items''. Should align with recipe_ingredients.unit and quantity convention.';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
-- 15. Pre-populate the master grocery items dictionary.
|
|
DO $$
|
|
DECLARE
|
|
fv_cat_id BIGINT;
|
|
ms_cat_id BIGINT;
|
|
de_cat_id BIGINT;
|
|
bb_cat_id BIGINT;
|
|
pdg_cat_id BIGINT;
|
|
bev_cat_id BIGINT;
|
|
ff_cat_id BIGINT;
|
|
snk_cat_id BIGINT;
|
|
hc_cat_id BIGINT;
|
|
pch_cat_id BIGINT;
|
|
bc_cat_id BIGINT;
|
|
ps_cat_id BIGINT;
|
|
dpf_cat_id BIGINT;
|
|
cg_cat_id BIGINT;
|
|
cs_cat_id BIGINT;
|
|
bkc_cat_id BIGINT;
|
|
BEGIN
|
|
SELECT id INTO fv_cat_id FROM public.categories WHERE name = 'Fruits & Vegetables';
|
|
SELECT id INTO ms_cat_id FROM public.categories WHERE name = 'Meat & Seafood';
|
|
SELECT id INTO de_cat_id FROM public.categories WHERE name = 'Dairy & Eggs';
|
|
SELECT id INTO bb_cat_id FROM public.categories WHERE name = 'Bakery & Bread';
|
|
SELECT id INTO pdg_cat_id FROM public.categories WHERE name = 'Pantry & Dry Goods';
|
|
SELECT id INTO bev_cat_id FROM public.categories WHERE name = 'Beverages';
|
|
SELECT id INTO ff_cat_id FROM public.categories WHERE name = 'Frozen Foods';
|
|
SELECT id INTO snk_cat_id FROM public.categories WHERE name = 'Snacks';
|
|
SELECT id INTO hc_cat_id FROM public.categories WHERE name = 'Household & Cleaning';
|
|
SELECT id INTO pch_cat_id FROM public.categories WHERE name = 'Personal Care & Health';
|
|
SELECT id INTO bc_cat_id FROM public.categories WHERE name = 'Baby & Child';
|
|
SELECT id INTO ps_cat_id FROM public.categories WHERE name = 'Pet Supplies';
|
|
SELECT id INTO dpf_cat_id FROM public.categories WHERE name = 'Deli & Prepared Foods';
|
|
SELECT id INTO cg_cat_id FROM public.categories WHERE name = 'Canned Goods';
|
|
SELECT id INTO cs_cat_id FROM public.categories WHERE name = 'Condiments & Spices';
|
|
SELECT id INTO bkc_cat_id FROM public.categories WHERE name = 'Breakfast & Cereal';
|
|
|
|
INSERT INTO public.master_grocery_items (name, category_id) VALUES
|
|
('apples', fv_cat_id), ('bananas', fv_cat_id), ('oranges', fv_cat_id), ('grapes', fv_cat_id), ('strawberries', fv_cat_id), ('blueberries', fv_cat_id), ('raspberries', fv_cat_id), ('avocados', fv_cat_id), ('tomatoes', fv_cat_id), ('potatoes', fv_cat_id), ('onions', fv_cat_id), ('garlic', fv_cat_id), ('carrots', fv_cat_id), ('broccoli', fv_cat_id), ('spinach', fv_cat_id), ('lettuce', fv_cat_id), ('bell peppers', fv_cat_id), ('cucumbers', fv_cat_id), ('mushrooms', fv_cat_id), ('lemons', fv_cat_id), ('limes', fv_cat_id), ('celery', fv_cat_id), ('corn', fv_cat_id), ('sweet potatoes', fv_cat_id), ('zucchini', fv_cat_id), ('cauliflower', fv_cat_id), ('green beans', fv_cat_id), ('peas', fv_cat_id), ('asparagus', fv_cat_id),
|
|
('chicken breast', ms_cat_id), ('chicken thighs', ms_cat_id), ('ground beef', ms_cat_id), ('steak', ms_cat_id), ('pork chops', ms_cat_id), ('bacon', ms_cat_id), ('sausage', ms_cat_id), ('salmon', ms_cat_id), ('shrimp', ms_cat_id), ('tilapia', ms_cat_id), ('cod', ms_cat_id), ('tuna', ms_cat_id), ('ham', ms_cat_id), ('turkey', ms_cat_id),
|
|
('milk', de_cat_id), ('cheese', de_cat_id), ('yogurt', de_cat_id), ('butter', de_cat_id), ('eggs', de_cat_id), ('cream cheese', de_cat_id), ('sour cream', de_cat_id), ('cottage cheese', de_cat_id),
|
|
('bread', bb_cat_id), ('bagels', bb_cat_id), ('tortillas', bb_cat_id), ('croissants', bb_cat_id), ('muffins', bb_cat_id), ('baguette', bb_cat_id), ('pita bread', bb_cat_id),
|
|
('rice', pdg_cat_id), ('pasta', pdg_cat_id), ('flour', pdg_cat_id), ('sugar', pdg_cat_id), ('salt', pdg_cat_id), ('pepper', pdg_cat_id), ('olive oil', pdg_cat_id), ('vegetable oil', pdg_cat_id), ('canned tomatoes', pdg_cat_id), ('canned beans', pdg_cat_id), ('peanut butter', pdg_cat_id), ('jam', pdg_cat_id), ('honey', pdg_cat_id), ('syrup', pdg_cat_id), ('nuts', pdg_cat_id), ('dried fruit', pdg_cat_id), ('crackers', pdg_cat_id), ('quinoa', pdg_cat_id), ('lentils', pdg_cat_id),
|
|
('water', bev_cat_id), ('juice', bev_cat_id), ('soda', bev_cat_id), ('coffee', bev_cat_id), ('tea', bev_cat_id), ('almond milk', bev_cat_id), ('soy milk', bev_cat_id), ('coconut water', bev_cat_id),
|
|
('frozen pizza', ff_cat_id), ('frozen vegetables', ff_cat_id), ('frozen fruit', ff_cat_id), ('ice cream', ff_cat_id), ('frozen dinners', ff_cat_id), ('french fries', ff_cat_id), ('frozen fish', ff_cat_id),
|
|
('chips', snk_cat_id), ('pretzels', snk_cat_id), ('popcorn', snk_cat_id), ('granola bars', snk_cat_id), ('cookies', snk_cat_id), ('chocolate', snk_cat_id), ('candy', snk_cat_id),
|
|
('paper towels', hc_cat_id), ('toilet paper', hc_cat_id), ('trash bags', hc_cat_id), ('dish soap', hc_cat_id), ('laundry detergent', hc_cat_id), ('all-purpose cleaner', hc_cat_id), ('sponges', hc_cat_id),
|
|
('soap', pch_cat_id), ('shampoo', pch_cat_id), ('conditioner', pch_cat_id), ('toothpaste', pch_cat_id), ('deodorant', pch_cat_id), ('vitamins', pch_cat_id), ('pain reliever', pch_cat_id),
|
|
('diapers', bc_cat_id), ('baby wipes', bc_cat_id), ('baby food', bc_cat_id), ('formula', bc_cat_id),
|
|
('dog food', ps_cat_id), ('cat food', ps_cat_id), ('cat litter', ps_cat_id),
|
|
('deli meat', dpf_cat_id), ('deli cheese', dpf_cat_id), ('rotisserie chicken', dpf_cat_id), ('prepared salads', dpf_cat_id),
|
|
('canned soup', cg_cat_id), ('canned corn', cg_cat_id), ('canned tuna', cg_cat_id), ('canned chicken', cg_cat_id),
|
|
('ketchup', cs_cat_id), ('mustard', cs_cat_id), ('mayonnaise', cs_cat_id), ('soy sauce', cs_cat_id), ('hot sauce', cs_cat_id), ('bbq sauce', cs_cat_id), ('salad dressing', cs_cat_id), ('cinnamon', cs_cat_id), ('oregano', cs_cat_id), ('paprika', cs_cat_id), ('garlic powder', cs_cat_id),
|
|
('cereal', bkc_cat_id), ('oatmeal', bkc_cat_id), ('granola', bkc_cat_id), ('pancake mix', bkc_cat_id)
|
|
ON CONFLICT (name) DO NOTHING;
|
|
END $$;
|
|
|
|
|
|
-- 16. Pre-populate initial watched items after master list exists.
|
|
DO $$
|
|
DECLARE
|
|
tp_id BIGINT;
|
|
ct_id BIGINT;
|
|
-- Note: Seeding user-specific data in a generic script is tricky.
|
|
-- This block is for demonstration. In a real app, users would add their own watched items.
|
|
-- We'll comment it out to avoid errors if no users exist.
|
|
-- sample_user_id UUID;
|
|
BEGIN
|
|
-- -- Find a user to assign watched items to.
|
|
-- SELECT id INTO sample_user_id FROM auth.users LIMIT 1;
|
|
|
|
-- -- If a user exists, add some default watched items for them.
|
|
-- IF sample_user_id IS NOT NULL THEN
|
|
-- SELECT id INTO tp_id FROM public.master_grocery_items WHERE name = 'toilet paper';
|
|
-- SELECT id INTO ct_id FROM public.master_grocery_items WHERE name = 'chicken thighs';
|
|
|
|
-- IF tp_id IS NOT NULL THEN
|
|
-- INSERT INTO public.user_watched_items (user_id, master_item_id)
|
|
-- VALUES (sample_user_id, tp_id)
|
|
-- ON CONFLICT (user_id, master_item_id) DO NOTHING;
|
|
-- END IF;
|
|
|
|
-- IF ct_id IS NOT NULL THEN
|
|
-- INSERT INTO public.user_watched_items (user_id, master_item_id)
|
|
-- VALUES (sample_user_id, ct_id)
|
|
-- ON CONFLICT (user_id, master_item_id) DO NOTHING;
|
|
-- END IF;
|
|
-- END IF;
|
|
NULL; -- PL/pgSQL block cannot be empty
|
|
END $$;
|
|
|
|
-- 17. Enable Row Level Security (RLS) on all tables. This is safe to run multiple times.
|
|
ALTER TABLE public.profiles ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE public.stores ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE public.categories ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE public.flyers ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE public.flyer_items ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE public.master_grocery_items ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE public.user_watched_items ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE public.user_alerts ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE public.notifications ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE public.item_price_history ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE public.master_item_aliases ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE public.shopping_lists ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE public.shopping_list_items ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE public.suggested_corrections ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE public.user_submitted_prices ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE public.brands ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE public.products ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE public.store_locations ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE public.flyer_locations ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE public.recipes ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE public.recipe_ingredients ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE public.menu_plans ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE public.planned_meals ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE public.tags ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE public.recipe_tags ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE public.recipe_ratings ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE public.pantry_items ENABLE ROW LEVEL SECURITY;
|
|
|
|
|
|
|
|
|
|
-- 18. Create policies. Using DROP/CREATE makes this script idempotent and safe to re-run.
|
|
-- Policies for 'profiles' table
|
|
-- Profiles are publicly readable
|
|
DROP POLICY IF EXISTS "Public profiles are viewable by everyone." ON public.profiles;
|
|
CREATE POLICY "Public profiles are viewable by everyone." ON public.profiles FOR SELECT USING (true);
|
|
-- Users can insert and update their own profile
|
|
DROP POLICY IF EXISTS "Users can update their own profile." ON public.profiles;
|
|
CREATE POLICY "Users can update their own profile." ON public.profiles FOR UPDATE USING (auth.uid() = id) WITH CHECK (auth.uid() = id);
|
|
|
|
|
|
-- Policies for 'stores' table
|
|
DROP POLICY IF EXISTS "Public read access" ON public.stores;
|
|
CREATE POLICY "Public read access" ON public.stores FOR SELECT USING (true);
|
|
|
|
-- Policies for 'categories' table
|
|
DROP POLICY IF EXISTS "Public read access" ON public.categories;
|
|
CREATE POLICY "Public read access" ON public.categories FOR SELECT USING (true);
|
|
|
|
-- Policies for 'flyers' table
|
|
DROP POLICY IF EXISTS "Public read access" ON public.flyers;
|
|
CREATE POLICY "Public read access" ON public.flyers FOR SELECT USING (true);
|
|
|
|
-- Policies for 'flyer_items' table
|
|
DROP POLICY IF EXISTS "Public read access" ON public.flyer_items;
|
|
CREATE POLICY "Public read access" ON public.flyer_items FOR SELECT USING (true);
|
|
|
|
-- Policies for 'master_grocery_items' table
|
|
DROP POLICY IF EXISTS "Public read access" ON public.master_grocery_items;
|
|
CREATE POLICY "Public read access" ON public.master_grocery_items FOR SELECT USING (true);
|
|
|
|
-- Policies for 'user_watched_items' table
|
|
-- Users can view their own watched items.
|
|
DROP POLICY IF EXISTS "Users can view their own watched items." ON public.user_watched_items;
|
|
CREATE POLICY "Users can view their own watched items." ON public.user_watched_items FOR SELECT USING (auth.uid() = user_id);
|
|
-- Users can insert, update, and delete their own watched items.
|
|
DROP POLICY IF EXISTS "Users can manage their own watched items." ON public.user_watched_items;
|
|
CREATE POLICY "Users can manage their own watched items." ON public.user_watched_items FOR ALL USING (auth.uid() = user_id) WITH CHECK (auth.uid() = user_id);
|
|
|
|
-- Policies for 'user_alerts' table
|
|
DROP POLICY IF EXISTS "Users can manage their own alerts" ON public.user_alerts;
|
|
CREATE POLICY "Users can manage their own alerts" ON public.user_alerts
|
|
FOR ALL USING (
|
|
-- Check that the user owns the parent 'user_watched_item'
|
|
auth.uid() = (SELECT user_id FROM public.user_watched_items WHERE id = user_watched_item_id)
|
|
);
|
|
|
|
-- Policies for 'notifications' table
|
|
DROP POLICY IF EXISTS "Users can manage their own notifications" ON public.notifications;
|
|
CREATE POLICY "Users can manage their own notifications" ON public.notifications
|
|
FOR ALL USING (auth.uid() = user_id) WITH CHECK (auth.uid() = user_id);
|
|
|
|
-- Policies for 'item_price_history' table
|
|
DROP POLICY IF EXISTS "Public read access for price history" ON public.item_price_history;
|
|
CREATE POLICY "Public read access for price history" ON public.item_price_history
|
|
FOR SELECT USING (true);
|
|
|
|
-- Policies for 'master_item_aliases' table
|
|
DROP POLICY IF EXISTS "Aliases are publicly viewable." ON public.master_item_aliases;
|
|
CREATE POLICY "Aliases are publicly viewable." ON public.master_item_aliases FOR SELECT USING (true);
|
|
-- Note: Write access to aliases should be restricted to an admin or trusted role.
|
|
|
|
-- Policies for 'shopping_lists' table
|
|
DROP POLICY IF EXISTS "Users can manage their own shopping lists." ON public.shopping_lists;
|
|
CREATE POLICY "Users can manage their own shopping lists." ON public.shopping_lists
|
|
FOR ALL USING (auth.uid() = user_id) WITH CHECK (auth.uid() = user_id);
|
|
|
|
-- Policies for 'shopping_list_items' table
|
|
DROP POLICY IF EXISTS "Users can manage items in their own shopping lists." ON public.shopping_list_items;
|
|
CREATE POLICY "Users can manage items in their own shopping lists." ON public.shopping_list_items
|
|
FOR ALL USING (
|
|
-- Check that the user owns the parent 'shopping_list'
|
|
auth.uid() = (SELECT user_id FROM public.shopping_lists WHERE id = shopping_list_id)
|
|
);
|
|
|
|
|
|
-- Policies for 'suggested_corrections' table
|
|
DROP POLICY IF EXISTS "Users can manage their own suggestions." ON public.suggested_corrections;
|
|
CREATE POLICY "Users can manage their own suggestions." ON public.suggested_corrections
|
|
FOR ALL USING (auth.uid() = user_id) WITH CHECK (auth.uid() = user_id);
|
|
-- Note: An admin role would need a separate policy to view/update all suggestions.
|
|
|
|
-- Policies for 'user_submitted_prices' table
|
|
DROP POLICY IF EXISTS "Submitted prices are publicly viewable." ON public.user_submitted_prices;
|
|
CREATE POLICY "Submitted prices are publicly viewable." ON public.user_submitted_prices
|
|
FOR SELECT USING (true);
|
|
|
|
DROP POLICY IF EXISTS "Users can manage their own submitted prices." ON public.user_submitted_prices;
|
|
CREATE POLICY "Users can manage their own submitted prices." ON public.user_submitted_prices
|
|
FOR INSERT, UPDATE, DELETE USING (auth.uid() = user_id) WITH CHECK (auth.uid() = user_id);
|
|
|
|
-- Policies for new product/location tables
|
|
DROP POLICY IF EXISTS "Public read access" ON public.brands;
|
|
CREATE POLICY "Public read access" ON public.brands FOR SELECT USING (true);
|
|
|
|
DROP POLICY IF EXISTS "Public read access" ON public.products;
|
|
CREATE POLICY "Public read access" ON public.products FOR SELECT USING (true);
|
|
|
|
DROP POLICY IF EXISTS "Public read access" ON public.store_locations;
|
|
CREATE POLICY "Public read access" ON public.store_locations FOR SELECT USING (true);
|
|
|
|
DROP POLICY IF EXISTS "Public read access" ON public.flyer_locations;
|
|
CREATE POLICY "Public read access" ON public.flyer_locations FOR SELECT USING (true);
|
|
|
|
-- Policies for recipe/menu plan tables
|
|
DROP POLICY IF EXISTS "Recipes are publicly viewable." ON public.recipes;
|
|
CREATE POLICY "Recipes are publicly viewable." ON public.recipes FOR SELECT USING (true);
|
|
|
|
DROP POLICY IF EXISTS "Users can manage their own recipes." ON public.recipes;
|
|
CREATE POLICY "Users can manage their own recipes." ON public.recipes FOR INSERT, UPDATE, DELETE USING (auth.uid() = user_id);
|
|
|
|
DROP POLICY IF EXISTS "Recipe ingredients are publicly viewable." ON public.recipe_ingredients;
|
|
CREATE POLICY "Recipe ingredients are publicly viewable." ON public.recipe_ingredients FOR SELECT USING (true);
|
|
-- Note: Write access to recipe_ingredients should be controlled by who owns the parent recipe.
|
|
-- A more complex policy or SECURITY DEFINER function would be needed for users to edit ingredients.
|
|
-- For now, we assume inserts/updates happen via a trusted process or admin role.
|
|
|
|
DROP POLICY IF EXISTS "Users can manage their own menu plans." ON public.menu_plans;
|
|
CREATE POLICY "Users can manage their own menu plans." ON public.menu_plans FOR ALL USING (auth.uid() = user_id);
|
|
|
|
DROP POLICY IF EXISTS "Users can manage meals in their own menu plans." ON public.planned_meals;
|
|
CREATE POLICY "Users can manage meals in their own menu plans." ON public.planned_meals
|
|
FOR ALL USING (
|
|
auth.uid() = (SELECT user_id FROM public.menu_plans WHERE id = menu_plan_id)
|
|
);
|
|
|
|
-- Policies for pantry_items table
|
|
DROP POLICY IF EXISTS "Users can manage their own pantry items." ON public.pantry_items;
|
|
CREATE POLICY "Users can manage their own pantry items." ON public.pantry_items FOR ALL USING (auth.uid() = user_id);
|
|
|
|
-- Policies for new recipe-related tables
|
|
DROP POLICY IF EXISTS "Tags are publicly viewable." ON public.tags;
|
|
CREATE POLICY "Tags are publicly viewable." ON public.tags FOR SELECT USING (true);
|
|
|
|
DROP POLICY IF EXISTS "Recipe-tag links are publicly viewable." ON public.recipe_tags;
|
|
CREATE POLICY "Recipe-tag links are publicly viewable." ON public.recipe_tags FOR SELECT USING (true);
|
|
|
|
DROP POLICY IF EXISTS "Recipe ratings are publicly viewable." ON public.recipe_ratings;
|
|
CREATE POLICY "Recipe ratings are publicly viewable." ON public.recipe_ratings FOR SELECT USING (true);
|
|
|
|
DROP POLICY IF EXISTS "Users can manage their own recipe ratings." ON public.recipe_ratings;
|
|
CREATE POLICY "Users can manage their own recipe ratings." ON public.recipe_ratings FOR ALL USING (auth.uid() = user_id);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|