database expansion prior to creating on server
This commit is contained in:
@@ -300,6 +300,13 @@ CREATE TABLE IF NOT EXISTS public.unmatched_flyer_items (
|
||||
);
|
||||
COMMENT ON TABLE public.unmatched_flyer_items IS 'A queue for reviewing flyer items that the system failed to automatically match.';
|
||||
|
||||
-- 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".';
|
||||
|
||||
|
||||
-- A table to store brand information.
|
||||
CREATE TABLE IF NOT EXISTS public.brands (
|
||||
@@ -324,7 +331,6 @@ COMMENT ON COLUMN public.products.upc_code IS 'Universal Product Code, if availa
|
||||
|
||||
-- Link flyer_items to the new products table.
|
||||
-- This is done via ALTER TABLE because 'products' is created after 'flyer_items'.
|
||||
ALTER TABLE public.flyer_items
|
||||
ADD CONSTRAINT flyer_items_product_id_fkey
|
||||
FOREIGN KEY (product_id) REFERENCES public.products(id);
|
||||
|
||||
@@ -371,7 +377,7 @@ CREATE TABLE IF NOT EXISTS public.recipes (
|
||||
protein_grams NUMERIC,
|
||||
fat_grams NUMERIC,
|
||||
carb_grams NUMERIC,
|
||||
avg_rating NUMERIC(2,1) DEFAULT 0.0 NOT NULL,
|
||||
avg_rating NUMERIC(2,1) DEFAULT 0.0,
|
||||
status TEXT DEFAULT 'private' NOT NULL CHECK (status IN ('private', 'pending_review', 'public')),
|
||||
rating_count INTEGER DEFAULT 0 NOT NULL,
|
||||
created_at TIMESTAMPTZ DEFAULT now() NOT NULL
|
||||
@@ -425,6 +431,14 @@ CREATE TABLE IF NOT EXISTS public.recipe_appliances (
|
||||
);
|
||||
COMMENT ON TABLE public.recipe_appliances IS 'Links recipes to the specific kitchen appliances they require.';
|
||||
|
||||
-- A linking table to 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,
|
||||
PRIMARY KEY (recipe_id, appliance_id)
|
||||
);
|
||||
COMMENT ON TABLE public.recipe_appliances IS 'Links recipes to the specific kitchen appliances they require.';
|
||||
|
||||
-- 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,
|
||||
@@ -444,6 +458,7 @@ CREATE TABLE IF NOT EXISTS public.recipe_comments (
|
||||
user_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE,
|
||||
parent_comment_id BIGINT REFERENCES public.recipe_comments(id) ON DELETE CASCADE, -- For threaded comments
|
||||
content TEXT NOT NULL,
|
||||
status TEXT DEFAULT 'visible' NOT NULL CHECK (status IN ('visible', 'hidden', 'reported')),
|
||||
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
||||
updated_at TIMESTAMPTZ
|
||||
);
|
||||
@@ -556,6 +571,17 @@ CREATE TABLE IF NOT EXISTS public.user_activity_log (
|
||||
);
|
||||
COMMENT ON TABLE public.user_activity_log IS 'Logs key user actions for analytics and behavior analysis.';
|
||||
|
||||
-- A generic table to log key user activities for analytics.
|
||||
CREATE TABLE IF NOT EXISTS public.user_activity_log (
|
||||
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
||||
user_id UUID REFERENCES public.users(id) ON DELETE SET NULL,
|
||||
activity_type TEXT NOT NULL,
|
||||
entity_id TEXT,
|
||||
details JSONB,
|
||||
created_at TIMESTAMPTZ DEFAULT now() NOT NULL
|
||||
);
|
||||
COMMENT ON TABLE public.user_activity_log IS 'Logs key user actions for analytics and behavior analysis.';
|
||||
|
||||
-- A table for users to group recipes into collections.
|
||||
CREATE TABLE IF NOT EXISTS public.recipe_collections (
|
||||
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
||||
@@ -591,6 +617,34 @@ CREATE TABLE IF NOT EXISTS public.user_dietary_restrictions (
|
||||
);
|
||||
COMMENT ON TABLE public.user_dietary_restrictions IS 'Connects users to their selected dietary needs and allergies.';
|
||||
|
||||
-- A table to 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_image_url TEXT NOT NULL,
|
||||
transaction_date TIMESTAMPTZ,
|
||||
total_amount_cents INTEGER,
|
||||
status TEXT DEFAULT 'pending' NOT NULL CHECK (status IN ('pending', 'processing', 'completed', 'failed')),
|
||||
raw_text TEXT,
|
||||
created_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
||||
processed_at TIMESTAMPTZ
|
||||
);
|
||||
COMMENT ON TABLE public.receipts IS 'Stores uploaded user receipts for purchase tracking and analysis.';
|
||||
|
||||
-- A table to 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,
|
||||
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),
|
||||
status TEXT DEFAULT 'unmatched' NOT NULL CHECK (status IN ('unmatched', 'matched', 'needs_review', 'ignored'))
|
||||
);
|
||||
COMMENT ON TABLE public.receipt_items IS 'Stores individual line items extracted from a user receipt.';
|
||||
|
||||
-- A table to store a predefined list of kitchen appliances.
|
||||
CREATE TABLE IF NOT EXISTS public.appliances (
|
||||
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
||||
@@ -664,6 +718,33 @@ COMMENT ON COLUMN public.shopping_trip_items.price_paid_cents IS 'The actual pri
|
||||
|
||||
|
||||
|
||||
-- A table to 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,
|
||||
completed_at TIMESTAMPTZ DEFAULT now() NOT NULL,
|
||||
total_spent_cents INTEGER
|
||||
);
|
||||
COMMENT ON TABLE public.shopping_trips IS 'A historical record of a completed shopping trip.';
|
||||
COMMENT ON COLUMN public.shopping_trips.total_spent_cents IS 'The total amount spent on this shopping trip, if provided by the user.';
|
||||
|
||||
-- A table to 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),
|
||||
custom_item_name TEXT,
|
||||
quantity NUMERIC NOT NULL,
|
||||
price_paid_cents INTEGER,
|
||||
CONSTRAINT trip_must_have_item_identifier CHECK (master_item_id IS NOT NULL OR custom_item_name IS NOT NULL)
|
||||
);
|
||||
COMMENT ON TABLE public.shopping_trip_items IS 'A historical log of items purchased during a shopping trip.';
|
||||
COMMENT ON COLUMN public.shopping_trip_items.price_paid_cents IS 'The actual price paid for the item during the trip, if provided.';
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -826,6 +907,12 @@ BEGIN
|
||||
ON CONFLICT (master_item_id, from_unit, to_unit) DO NOTHING;
|
||||
END $$;
|
||||
|
||||
-- Pre-populate the appliances table.
|
||||
INSERT INTO public.appliances (name) VALUES
|
||||
('Oven'), ('Microwave'), ('Stovetop'), ('Blender'), ('Food Processor'), ('Stand Mixer'), ('Hand Mixer'), ('Air Fryer'), ('Instant Pot'), ('Slow Cooker'), ('Grill'), ('Toaster')
|
||||
ON CONFLICT (name) DO NOTHING;
|
||||
END $$;
|
||||
|
||||
-- Pre-populate the dietary_restrictions table.
|
||||
INSERT INTO public.dietary_restrictions (name, type) VALUES
|
||||
('Vegetarian', 'diet'), ('Vegan', 'diet'), ('Gluten-Free', 'diet'), ('Keto', 'diet'),
|
||||
@@ -1443,6 +1530,225 @@ AS $$
|
||||
ORDER BY r.name ASC;
|
||||
$$;
|
||||
|
||||
-- Function to get a paginated list of recent activities for the audit log.
|
||||
CREATE OR REPLACE FUNCTION public.get_activity_log(p_limit INTEGER DEFAULT 20, p_offset INTEGER DEFAULT 0)
|
||||
RETURNS TABLE (
|
||||
id BIGINT,
|
||||
user_id UUID,
|
||||
activity_type TEXT,
|
||||
entity_id TEXT,
|
||||
details JSONB,
|
||||
created_at TIMESTAMPTZ,
|
||||
user_full_name TEXT,
|
||||
user_avatar_url TEXT
|
||||
)
|
||||
LANGUAGE sql
|
||||
STABLE
|
||||
SECURITY INVOKER
|
||||
AS $$
|
||||
SELECT
|
||||
al.id,
|
||||
al.user_id,
|
||||
al.activity_type,
|
||||
al.entity_id,
|
||||
al.details,
|
||||
al.created_at,
|
||||
p.full_name AS user_full_name,
|
||||
p.avatar_url AS user_avatar_url
|
||||
FROM public.user_activity_log al
|
||||
-- Join with profiles to get user details for display.
|
||||
-- LEFT JOIN is used because some activities might be system-generated (user_id is NULL).
|
||||
LEFT JOIN public.profiles p ON al.user_id = p.id
|
||||
ORDER BY
|
||||
al.created_at DESC
|
||||
LIMIT p_limit
|
||||
OFFSET p_offset;
|
||||
$$;
|
||||
|
||||
-- Function to get recipes that are compatible with a user's dietary restrictions (allergies).
|
||||
-- It filters out any recipe containing an ingredient that the user is allergic to.
|
||||
CREATE OR REPLACE FUNCTION public.get_recipes_for_user_diets(p_user_id UUID)
|
||||
RETURNS SETOF public.recipes
|
||||
LANGUAGE sql
|
||||
STABLE
|
||||
SECURITY INVOKER
|
||||
AS $$
|
||||
WITH UserAllergens AS (
|
||||
-- CTE 1: Find all master item IDs that are allergens for the given user.
|
||||
SELECT mgi.id
|
||||
FROM public.master_grocery_items mgi
|
||||
JOIN public.dietary_restrictions dr ON mgi.allergy_info->>'type' = dr.name
|
||||
JOIN public.user_dietary_restrictions udr ON dr.id = udr.restriction_id
|
||||
WHERE udr.user_id = p_user_id
|
||||
AND dr.type = 'allergy'
|
||||
AND mgi.is_allergen = true
|
||||
),
|
||||
ForbiddenRecipes AS (
|
||||
-- CTE 2: Find all recipe IDs that contain one or more of the user's allergens.
|
||||
SELECT DISTINCT ri.recipe_id
|
||||
FROM public.recipe_ingredients ri
|
||||
WHERE ri.master_item_id IN (SELECT id FROM UserAllergens)
|
||||
)
|
||||
-- Final Selection: Return all recipes that are NOT in the forbidden list.
|
||||
SELECT *
|
||||
FROM public.recipes r
|
||||
WHERE r.id NOT IN (SELECT recipe_id FROM ForbiddenRecipes)
|
||||
ORDER BY r.avg_rating DESC, r.name ASC;
|
||||
$$;
|
||||
|
||||
-- Function to get a personalized activity feed for a user based on who they follow.
|
||||
-- It aggregates recent activities from followed users.
|
||||
CREATE OR REPLACE FUNCTION public.get_user_feed(p_user_id UUID, p_limit INTEGER DEFAULT 20, p_offset INTEGER DEFAULT 0)
|
||||
RETURNS TABLE (
|
||||
id BIGINT,
|
||||
user_id UUID,
|
||||
activity_type TEXT,
|
||||
entity_id TEXT,
|
||||
details JSONB,
|
||||
created_at TIMESTAMPTZ,
|
||||
user_full_name TEXT,
|
||||
user_avatar_url TEXT
|
||||
)
|
||||
LANGUAGE sql
|
||||
STABLE
|
||||
SECURITY INVOKER
|
||||
AS $$
|
||||
WITH FollowedUsers AS (
|
||||
-- CTE 1: Get the IDs of all users that the current user is following.
|
||||
SELECT following_id FROM public.user_follows WHERE follower_id = p_user_id
|
||||
)
|
||||
-- Final Selection: Get activities from the log where the user_id is in the followed list.
|
||||
SELECT
|
||||
al.id,
|
||||
al.user_id,
|
||||
al.activity_type,
|
||||
al.entity_id,
|
||||
al.details,
|
||||
al.created_at,
|
||||
p.full_name AS user_full_name,
|
||||
p.avatar_url AS user_avatar_url
|
||||
FROM public.user_activity_log al
|
||||
JOIN public.profiles p ON al.user_id = p.id
|
||||
WHERE
|
||||
al.user_id IN (SELECT following_id FROM FollowedUsers)
|
||||
-- We can filter for specific activity types to make the feed more relevant.
|
||||
AND al.activity_type IN (
|
||||
'new_recipe',
|
||||
'favorite_recipe',
|
||||
'share_shopping_list'
|
||||
-- 'new_recipe_rating' could be added here later
|
||||
)
|
||||
ORDER BY
|
||||
al.created_at DESC
|
||||
LIMIT p_limit
|
||||
OFFSET p_offset;
|
||||
$$;
|
||||
|
||||
-- Function to archive a shopping list into a historical shopping trip.
|
||||
-- It creates a shopping_trip record, copies purchased items to shopping_trip_items,
|
||||
-- and then deletes the purchased items from the original shopping list.
|
||||
CREATE OR REPLACE FUNCTION public.complete_shopping_list(
|
||||
p_shopping_list_id BIGINT,
|
||||
p_user_id UUID,
|
||||
p_total_spent_cents INTEGER DEFAULT NULL
|
||||
)
|
||||
RETURNS BIGINT -- Returns the ID of the new shopping_trip record.
|
||||
LANGUAGE plpgsql
|
||||
SECURITY DEFINER
|
||||
AS $$
|
||||
DECLARE
|
||||
list_owner_id UUID;
|
||||
new_trip_id BIGINT;
|
||||
BEGIN
|
||||
-- Security Check: Ensure the user calling this function owns the target shopping list.
|
||||
SELECT user_id INTO list_owner_id
|
||||
FROM public.shopping_lists
|
||||
WHERE id = p_shopping_list_id;
|
||||
|
||||
IF list_owner_id IS NULL OR list_owner_id <> p_user_id THEN
|
||||
RAISE EXCEPTION 'Permission denied: You do not own shopping list %', p_shopping_list_id;
|
||||
END IF;
|
||||
|
||||
-- 1. Create a new shopping_trip record.
|
||||
INSERT INTO public.shopping_trips (user_id, shopping_list_id, total_spent_cents)
|
||||
VALUES (p_user_id, p_shopping_list_id, p_total_spent_cents)
|
||||
RETURNING id INTO new_trip_id;
|
||||
|
||||
-- 2. Copy purchased items from the shopping list to the new shopping_trip_items table.
|
||||
INSERT INTO public.shopping_trip_items (shopping_trip_id, master_item_id, custom_item_name, quantity)
|
||||
SELECT new_trip_id, master_item_id, custom_item_name, quantity
|
||||
FROM public.shopping_list_items
|
||||
WHERE shopping_list_id = p_shopping_list_id AND is_purchased = true;
|
||||
|
||||
-- 3. Delete the purchased items from the original shopping list.
|
||||
DELETE FROM public.shopping_list_items
|
||||
WHERE shopping_list_id = p_shopping_list_id AND is_purchased = true;
|
||||
|
||||
RETURN new_trip_id;
|
||||
END;
|
||||
$$;
|
||||
|
||||
-- Function to find better deals for items on a recently processed receipt.
|
||||
-- It compares the price paid on the receipt with current flyer prices.
|
||||
CREATE OR REPLACE FUNCTION public.find_deals_for_receipt_items(p_receipt_id BIGINT)
|
||||
RETURNS TABLE (
|
||||
receipt_item_id BIGINT,
|
||||
master_item_id BIGINT,
|
||||
item_name TEXT,
|
||||
price_paid_cents INTEGER,
|
||||
current_best_price_in_cents INTEGER,
|
||||
potential_savings_cents INTEGER,
|
||||
deal_store_name TEXT,
|
||||
flyer_id BIGINT
|
||||
)
|
||||
LANGUAGE sql
|
||||
STABLE
|
||||
SECURITY INVOKER
|
||||
AS $$
|
||||
WITH ReceiptItems AS (
|
||||
-- CTE 1: Get all matched items from the specified receipt.
|
||||
SELECT
|
||||
ri.id AS receipt_item_id,
|
||||
ri.master_item_id,
|
||||
mgi.name AS item_name,
|
||||
ri.price_paid_cents
|
||||
FROM public.receipt_items ri
|
||||
JOIN public.master_grocery_items mgi ON ri.master_item_id = mgi.id
|
||||
WHERE ri.receipt_id = p_receipt_id
|
||||
AND ri.master_item_id IS NOT NULL
|
||||
),
|
||||
BestCurrentPrices AS (
|
||||
-- CTE 2: Find the single best price for every item currently on sale.
|
||||
SELECT DISTINCT ON (fi.master_item_id)
|
||||
fi.master_item_id,
|
||||
fi.price_in_cents,
|
||||
s.name AS store_name,
|
||||
f.id AS flyer_id
|
||||
FROM public.flyer_items fi
|
||||
JOIN public.flyers f ON fi.flyer_id = f.id
|
||||
JOIN public.stores s ON f.store_id = s.id
|
||||
WHERE fi.master_item_id IS NOT NULL
|
||||
AND fi.price_in_cents IS NOT NULL
|
||||
AND CURRENT_DATE BETWEEN f.valid_from AND f.valid_to
|
||||
ORDER BY fi.master_item_id, fi.price_in_cents ASC
|
||||
)
|
||||
-- Final Selection: Join receipt items with current deals and find savings.
|
||||
SELECT
|
||||
ri.receipt_item_id,
|
||||
ri.master_item_id,
|
||||
ri.item_name,
|
||||
ri.price_paid_cents,
|
||||
bcp.price_in_cents AS current_best_price_in_cents,
|
||||
(ri.price_paid_cents - bcp.price_in_cents) AS potential_savings_cents,
|
||||
bcp.store_name AS deal_store_name,
|
||||
bcp.flyer_id
|
||||
FROM ReceiptItems ri
|
||||
JOIN BestCurrentPrices bcp ON ri.master_item_id = bcp.master_item_id
|
||||
-- Only return rows where the current sale price is better than the price paid.
|
||||
WHERE bcp.price_in_cents < ri.price_paid_cents
|
||||
ORDER BY potential_savings_cents DESC;
|
||||
$$;
|
||||
|
||||
-- Function to approve a suggested correction and apply it.
|
||||
-- This is a SECURITY DEFINER function to allow an admin to update tables
|
||||
-- they might not have direct RLS access to.
|
||||
@@ -1514,6 +1820,10 @@ BEGIN
|
||||
-- Also create a default shopping list for the new user.
|
||||
INSERT INTO public.shopping_lists (user_id, name)
|
||||
VALUES (new_profile_id, 'Main Shopping List');
|
||||
|
||||
-- Log the new user event
|
||||
INSERT INTO public.user_activity_log (user_id, activity_type, entity_id, details)
|
||||
VALUES (new.id, 'new_user', new.id, jsonb_build_object('full_name', user_meta_data->>'full_name'));
|
||||
RETURN new;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
@@ -1758,6 +2068,50 @@ CREATE TRIGGER on_new_list_share
|
||||
AFTER INSERT ON public.shared_shopping_lists
|
||||
FOR EACH ROW EXECUTE FUNCTION public.log_new_list_share();
|
||||
|
||||
-- 8. Trigger to log when a user favorites a recipe.
|
||||
CREATE OR REPLACE FUNCTION public.log_new_favorite_recipe()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
INSERT INTO public.user_activity_log (user_id, activity_type, entity_id, details)
|
||||
VALUES (
|
||||
NEW.user_id,
|
||||
'favorite_recipe',
|
||||
NEW.recipe_id::text,
|
||||
jsonb_build_object(
|
||||
'recipe_name', (SELECT name FROM public.recipes WHERE id = NEW.recipe_id)
|
||||
)
|
||||
);
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
DROP TRIGGER IF EXISTS on_new_favorite_recipe ON public.favorite_recipes;
|
||||
CREATE TRIGGER on_new_favorite_recipe
|
||||
AFTER INSERT ON public.favorite_recipes
|
||||
FOR EACH ROW EXECUTE FUNCTION public.log_new_favorite_recipe();
|
||||
|
||||
-- 9. Trigger to log when a user shares a shopping list.
|
||||
CREATE OR REPLACE FUNCTION public.log_new_list_share()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
INSERT INTO public.user_activity_log (user_id, activity_type, entity_id, details)
|
||||
VALUES (
|
||||
NEW.shared_by_user_id,
|
||||
'share_shopping_list',
|
||||
NEW.shopping_list_id::text,
|
||||
jsonb_build_object(
|
||||
'list_name', (SELECT name FROM public.shopping_lists WHERE id = NEW.shopping_list_id),
|
||||
'shared_with_name', (SELECT full_name FROM public.profiles WHERE id = NEW.shared_with_user_id)
|
||||
)
|
||||
);
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
DROP TRIGGER IF EXISTS on_new_list_share ON public.shared_shopping_lists;
|
||||
CREATE TRIGGER on_new_list_share
|
||||
AFTER INSERT ON public.shared_shopping_lists
|
||||
FOR EACH ROW EXECUTE FUNCTION public.log_new_list_share();
|
||||
-- Function to get recipes that are compatible with a user's dietary restrictions (allergies).
|
||||
-- It filters out any recipe containing an ingredient that the user is allergic to.
|
||||
CREATE OR REPLACE FUNCTION public.get_recipes_for_user_diets(p_user_id UUID)
|
||||
|
||||
Reference in New Issue
Block a user