439 lines
21 KiB
SQL
439 lines
21 KiB
SQL
|
|
-- DONE
|
|
-- ============================================================================
|
|
-- PART 0.5: USER AUTHENTICATION TABLE
|
|
-- ============================================================================
|
|
-- This replaces the Supabase `auth.users` table.
|
|
CREATE TABLE IF NOT EXISTS public.users (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
email TEXT NOT NULL UNIQUE,
|
|
password_hash TEXT NOT NULL,
|
|
refresh_token TEXT, -- Stores the long-lived refresh token for re-authentication.
|
|
created_at TIMESTAMPTZ DEFAULT now() NOT NULL
|
|
);
|
|
COMMENT ON TABLE public.users IS 'Stores user authentication information, replacing Supabase auth.';
|
|
|
|
-- Add an index on the refresh_token for faster lookups when refreshing tokens.
|
|
CREATE INDEX IF NOT EXISTS idx_users_refresh_token ON public.users(refresh_token);
|
|
|
|
-- 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 public.users(id) ON DELETE CASCADE,
|
|
updated_at TIMESTAMPTZ,
|
|
full_name TEXT,
|
|
avatar_url TEXT,
|
|
preferences JSONB,
|
|
role TEXT CHECK (role IN ('admin', 'user'))
|
|
);
|
|
COMMENT ON TABLE public.profiles IS 'Stores public-facing user data, linked to the private auth.users table.';
|
|
|
|
-- 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
|
|
);
|
|
COMMENT ON TABLE public.stores IS 'Stores metadata for grocery store chains (e.g., Safeway, Kroger).';
|
|
|
|
-- 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,
|
|
name TEXT NOT NULL UNIQUE
|
|
);
|
|
COMMENT ON TABLE public.categories IS 'Stores a predefined list of grocery item categories (e.g., ''Fruits & Vegetables'', ''Dairy & Eggs'').';
|
|
|
|
-- 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 NOT NULL,
|
|
image_url TEXT NOT NULL,
|
|
checksum TEXT UNIQUE,
|
|
store_id BIGINT REFERENCES public.stores(id),
|
|
valid_from DATE,
|
|
valid_to DATE,
|
|
store_address TEXT
|
|
);
|
|
COMMENT ON TABLE public.flyers IS 'Stores metadata for each processed flyer, linking it to a store and its validity period.';
|
|
|
|
-- 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)
|
|
);
|
|
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.';
|
|
|
|
-- 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,
|
|
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,
|
|
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
|
UNIQUE(user_id, master_item_id)
|
|
);
|
|
COMMENT ON TABLE public.user_watched_items IS 'A linking table that represents a user''s personal watchlist of grocery items.';
|
|
|
|
-- 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,
|
|
flyer_id BIGINT REFERENCES public.flyers(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_name TEXT, -- Denormalized for easier display
|
|
unit_price JSONB,
|
|
master_item_id BIGINT REFERENCES public.master_grocery_items(id),
|
|
product_id BIGINT -- Future use for specific product linking
|
|
);
|
|
COMMENT ON TABLE public.flyer_items IS 'Stores individual items extracted from a specific flyer.';
|
|
|
|
-- 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 CHECK (alert_type IN ('PRICE_BELOW', 'PERCENT_OFF_AVERAGE')),
|
|
threshold_value NUMERIC NOT NULL,
|
|
is_active BOOLEAN DEFAULT true NOT NULL,
|
|
created_at TIMESTAMPTZ DEFAULT now() NOT NULL
|
|
);
|
|
COMMENT ON TABLE public.user_alerts IS 'Stores user-configured alert rules for their watched items.';
|
|
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.users(id) ON DELETE CASCADE,
|
|
content TEXT NOT NULL,
|
|
link_url TEXT,
|
|
is_read BOOLEAN DEFAULT false NOT NULL,
|
|
created_at TIMESTAMPTZ DEFAULT now() NOT NULL
|
|
);
|
|
COMMENT ON TABLE public.notifications IS 'A central log of notifications generated for users, such as price alerts.';
|
|
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,
|
|
min_price_in_cents INTEGER,
|
|
max_price_in_cents INTEGER,
|
|
avg_price_in_cents INTEGER,
|
|
data_points_count INTEGER DEFAULT 0 NOT NULL,
|
|
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.';
|
|
COMMENT ON COLUMN public.item_price_history.min_price_in_cents IS 'The lowest price found for this item on this day, in cents,';
|
|
COMMENT ON COLUMN public.item_price_history.max_price_in_cents IS 'The highest price found for this item on this day, in cents.';
|
|
COMMENT ON COLUMN public.item_price_history.avg_price_in_cents IS 'The average price found for this item on this day, in cents.';
|
|
COMMENT ON COLUMN public.item_price_history.data_points_count IS 'How many data points were used for this summary.';
|
|
|
|
|
|
-- 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.users(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,
|
|
quantity NUMERIC DEFAULT 1 NOT NULL,
|
|
is_purchased BOOLEAN DEFAULT false NOT NULL,
|
|
added_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
|
-- 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.users(id),
|
|
correction_type TEXT NOT NULL,
|
|
suggested_value TEXT NOT NULL,
|
|
status TEXT DEFAULT 'pending' NOT NULL,
|
|
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
|
reviewed_notes TEXT,
|
|
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.users(id),
|
|
master_item_id BIGINT NOT NULL REFERENCES public.master_grocery_items(id),
|
|
store_id BIGINT NOT NULL REFERENCES public.stores(id),
|
|
price_in_cents INTEGER NOT NULL,
|
|
photo_url TEXT,
|
|
upvotes INTEGER DEFAULT 0 NOT NULL,
|
|
downvotes INTEGER DEFAULT 0 NOT NULL,
|
|
created_at TIMESTAMPTZ DEFAULT now() NOT NULL
|
|
);
|
|
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),
|
|
name TEXT NOT NULL,
|
|
description TEXT,
|
|
size TEXT,
|
|
upc_code TEXT UNIQUE
|
|
);
|
|
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.';
|
|
COMMENT ON COLUMN public.products.brand_id IS 'Can be null for generic/store-brand items.';
|
|
COMMENT ON COLUMN public.products.name IS 'Prime Raised without Antibiotics Chicken Breast.';
|
|
COMMENT ON COLUMN public.products.size IS 'e.g., "4L", "500g".';
|
|
|
|
|
|
-- 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);
|
|
ADD CONSTRAINT flyer_items_product_id_fkey
|
|
FOREIGN KEY (product_id) 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.
|
|
-- This requires the pg_trgm extension.
|
|
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.';
|
|
-- Add a GIST index for efficient geographic queries.
|
|
-- This requires the postgis extension.
|
|
CREATE INDEX IF NOT EXISTS store_locations_geo_idx ON public.store_locations USING GIST (location);
|
|
|
|
-- 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)
|
|
);
|
|
COMMENT ON TABLE public.flyer_locations IS 'A linking table associating a single flyer with multiple store locations where its deals are valid.';
|
|
|
|
-- 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.users(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,
|
|
photo_url TEXT,
|
|
-- Optional nutritional information
|
|
calories_per_serving INTEGER,
|
|
protein_grams NUMERIC,
|
|
fat_grams NUMERIC,
|
|
carb_grams NUMERIC,
|
|
-- Aggregated rating data for fast sorting/display
|
|
avg_rating NUMERIC(2,1) DEFAULT 0.0 NOT NULL,
|
|
rating_count INTEGER DEFAULT 0 NOT NULL,
|
|
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
|
|
);
|
|
COMMENT ON TABLE public.recipe_ingredients IS 'Defines the ingredients and quantities needed for a recipe.';
|
|
COMMENT ON COLUMN public.recipe_ingredients.unit IS 'e.g., "cups", "tbsp", "g", "each".';
|
|
|
|
-- 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)
|
|
);
|
|
COMMENT ON TABLE public.recipe_tags IS 'A linking table to associate multiple tags with a single recipe.';
|
|
|
|
-- 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.
|
|
);
|
|
COMMENT ON TABLE public.recipe_ratings IS 'Stores individual user ratings for recipes, ensuring 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,
|
|
meal_type TEXT NOT NULL,
|
|
servings_to_cook INTEGER
|
|
);
|
|
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,
|
|
best_before_date DATE,
|
|
updated_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
|
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.';
|
|
|
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_password_reset_tokens_token_hash ON public.password_reset_tokens(token_hash);
|
|
-- A table to 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,
|
|
token_hash TEXT NOT NULL UNIQUE,
|
|
expires_at TIMESTAMPTZ NOT NULL,
|
|
created_at TIMESTAMPTZ DEFAULT now() NOT NULL
|
|
);
|
|
COMMENT ON TABLE public.password_reset_tokens IS 'Stores secure, single-use tokens for password reset requests.';
|
|
COMMENT ON COLUMN public.password_reset_tokens.token_hash IS 'A bcrypt hash of the reset token sent to the user.';
|
|
COMMENT ON COLUMN public.password_reset_tokens.expires_at IS 'The timestamp when this token is no longer valid.';
|
|
|
|
-- A table to 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,
|
|
from_unit TEXT NOT NULL,
|
|
to_unit TEXT NOT NULL,
|
|
factor NUMERIC NOT NULL,
|
|
UNIQUE(master_item_id, from_unit, to_unit)
|
|
);
|
|
COMMENT ON TABLE public.unit_conversions IS 'Stores item-specific unit conversion factors (e.g., grams of flour to cups).';
|
|
COMMENT ON COLUMN public.unit_conversions.factor IS 'The multiplication factor to convert from_unit to to_unit.';
|