Compare commits
4 Commits
main
...
436f92cafc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
436f92cafc | ||
|
|
9f421907eb | ||
|
|
37dc26bb14 | ||
|
|
441266683c |
282
supabase/migrations/001_initial_schema.sql
Normal file
282
supabase/migrations/001_initial_schema.sql
Normal file
@@ -0,0 +1,282 @@
|
|||||||
|
-- Migration: Initial database schema
|
||||||
|
-- Created: 2026-02-09
|
||||||
|
-- Issue: #13
|
||||||
|
-- Description: Creates core tables for Pantry app (inventory, products, tags, units)
|
||||||
|
|
||||||
|
-- ======================
|
||||||
|
-- ENUM TYPES
|
||||||
|
-- ======================
|
||||||
|
|
||||||
|
CREATE TYPE tag_category AS ENUM (
|
||||||
|
'position', -- Location: fridge, freezer, pantry
|
||||||
|
'type', -- Food type: dairy, meat, vegan
|
||||||
|
'dietary', -- Diet: gluten-free, vegan, organic
|
||||||
|
'custom' -- User-defined
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TYPE unit_type AS ENUM (
|
||||||
|
'weight', -- kg, g, lb, oz
|
||||||
|
'volume', -- L, mL, cup, tbsp
|
||||||
|
'count', -- pcs, items (no conversion)
|
||||||
|
'custom' -- can, jar, bottle (user-defined)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- ======================
|
||||||
|
-- BASE TABLES (no foreign keys)
|
||||||
|
-- ======================
|
||||||
|
|
||||||
|
-- Units: measurement units with conversion support
|
||||||
|
CREATE TABLE units (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
|
||||||
|
-- Core data
|
||||||
|
name TEXT NOT NULL, -- "kilogram", "liter", "piece"
|
||||||
|
abbreviation TEXT NOT NULL, -- "kg", "L", "pcs"
|
||||||
|
unit_type unit_type NOT NULL,
|
||||||
|
|
||||||
|
-- Conversion system
|
||||||
|
base_unit_id UUID REFERENCES units(id), -- NULL = this is a base unit
|
||||||
|
conversion_factor DECIMAL(20,10), -- Factor to convert to base unit
|
||||||
|
|
||||||
|
-- Metadata
|
||||||
|
is_default BOOLEAN DEFAULT false, -- Shipped with app
|
||||||
|
created_by UUID REFERENCES auth.users(id), -- NULL = system unit
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
|
|
||||||
|
CONSTRAINT unique_unit_abbr UNIQUE (abbreviation, created_by),
|
||||||
|
CONSTRAINT check_conversion_logic CHECK (
|
||||||
|
(base_unit_id IS NULL AND conversion_factor IS NULL) OR
|
||||||
|
(base_unit_id IS NOT NULL AND conversion_factor IS NOT NULL)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Tags: flexible labeling system
|
||||||
|
CREATE TABLE tags (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
|
||||||
|
-- Core data
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
category tag_category NOT NULL DEFAULT 'custom',
|
||||||
|
|
||||||
|
-- Visual
|
||||||
|
icon TEXT, -- Emoji or icon name: "🧊", "cheese"
|
||||||
|
color TEXT, -- Hex color: "#3b82f6"
|
||||||
|
|
||||||
|
-- Ownership
|
||||||
|
created_by UUID REFERENCES auth.users(id), -- NULL = system tag
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
|
|
||||||
|
CONSTRAINT unique_tag_name UNIQUE (name, created_by)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- ======================
|
||||||
|
-- MAIN TABLES
|
||||||
|
-- ======================
|
||||||
|
|
||||||
|
-- Products: cached product data from Open Food Facts
|
||||||
|
CREATE TABLE products (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
|
||||||
|
-- Open Food Facts data
|
||||||
|
barcode TEXT UNIQUE NOT NULL,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
brand TEXT,
|
||||||
|
image_url TEXT,
|
||||||
|
image_small_url TEXT, -- Thumbnail
|
||||||
|
|
||||||
|
-- Categories from Open Food Facts
|
||||||
|
categories TEXT[], -- Array: ['dairy', 'milk']
|
||||||
|
|
||||||
|
-- Nutrition (optional, for future features)
|
||||||
|
nutrition JSONB, -- Full nutrition data
|
||||||
|
|
||||||
|
-- Defaults
|
||||||
|
default_unit_id UUID REFERENCES units(id),
|
||||||
|
default_quantity DECIMAL(10,2), -- E.g., 1L bottle
|
||||||
|
|
||||||
|
-- Metadata
|
||||||
|
source TEXT DEFAULT 'openfoodfacts',
|
||||||
|
cached_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
|
last_fetched TIMESTAMPTZ,
|
||||||
|
|
||||||
|
-- Quality score (from Open Food Facts)
|
||||||
|
completeness_score INTEGER CHECK (completeness_score BETWEEN 0 AND 100)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Inventory Items: actual items in your kitchen right now
|
||||||
|
CREATE TABLE inventory_items (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
|
||||||
|
-- Product reference (nullable for custom items)
|
||||||
|
product_id UUID REFERENCES products(id) ON DELETE SET NULL,
|
||||||
|
|
||||||
|
-- Core data
|
||||||
|
name TEXT NOT NULL, -- Product name or custom name
|
||||||
|
quantity DECIMAL(10,2) NOT NULL CHECK (quantity >= 0),
|
||||||
|
unit_id UUID NOT NULL REFERENCES units(id),
|
||||||
|
|
||||||
|
-- Optional metadata
|
||||||
|
expiry_date DATE,
|
||||||
|
location TEXT, -- Free text: "top shelf", "door", etc.
|
||||||
|
notes TEXT,
|
||||||
|
|
||||||
|
-- Audit trail
|
||||||
|
added_by UUID NOT NULL REFERENCES auth.users(id),
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Item Tags: many-to-many relationship between items and tags
|
||||||
|
CREATE TABLE item_tags (
|
||||||
|
item_id UUID NOT NULL REFERENCES inventory_items(id) ON DELETE CASCADE,
|
||||||
|
tag_id UUID NOT NULL REFERENCES tags(id) ON DELETE CASCADE,
|
||||||
|
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
|
|
||||||
|
PRIMARY KEY (item_id, tag_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- User Profiles: additional user metadata
|
||||||
|
CREATE TABLE user_profiles (
|
||||||
|
id UUID PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE,
|
||||||
|
|
||||||
|
display_name TEXT,
|
||||||
|
avatar_url TEXT,
|
||||||
|
|
||||||
|
-- Preferences
|
||||||
|
default_unit_system TEXT DEFAULT 'metric', -- 'metric' or 'imperial'
|
||||||
|
theme TEXT DEFAULT 'auto', -- 'light', 'dark', 'auto'
|
||||||
|
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
-- ======================
|
||||||
|
-- INDEXES
|
||||||
|
-- ======================
|
||||||
|
|
||||||
|
-- Units
|
||||||
|
CREATE INDEX idx_units_type ON units(unit_type);
|
||||||
|
CREATE INDEX idx_units_base ON units(base_unit_id);
|
||||||
|
|
||||||
|
-- Tags
|
||||||
|
CREATE INDEX idx_tags_category ON tags(category);
|
||||||
|
CREATE INDEX idx_tags_created_by ON tags(created_by);
|
||||||
|
|
||||||
|
-- Products
|
||||||
|
CREATE UNIQUE INDEX idx_products_barcode ON products(barcode);
|
||||||
|
CREATE INDEX idx_products_name ON products USING GIN (to_tsvector('english', name));
|
||||||
|
CREATE INDEX idx_products_search ON products
|
||||||
|
USING GIN (to_tsvector('english', name || ' ' || COALESCE(brand, '')));
|
||||||
|
|
||||||
|
-- Inventory Items
|
||||||
|
CREATE INDEX idx_items_product ON inventory_items(product_id);
|
||||||
|
CREATE INDEX idx_items_added_by ON inventory_items(added_by);
|
||||||
|
CREATE INDEX idx_items_expiry ON inventory_items(expiry_date) WHERE expiry_date IS NOT NULL;
|
||||||
|
|
||||||
|
-- Item Tags
|
||||||
|
CREATE INDEX idx_item_tags_tag ON item_tags(tag_id);
|
||||||
|
CREATE INDEX idx_item_tags_item ON item_tags(item_id);
|
||||||
|
|
||||||
|
-- ======================
|
||||||
|
-- FUNCTIONS
|
||||||
|
-- ======================
|
||||||
|
|
||||||
|
-- Auto-update timestamp trigger function
|
||||||
|
CREATE OR REPLACE FUNCTION update_updated_at()
|
||||||
|
RETURNS TRIGGER AS $$
|
||||||
|
BEGIN
|
||||||
|
NEW.updated_at = NOW();
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
-- Unit conversion function
|
||||||
|
CREATE OR REPLACE FUNCTION convert_unit(
|
||||||
|
quantity DECIMAL,
|
||||||
|
from_unit_id UUID,
|
||||||
|
to_unit_id UUID
|
||||||
|
)
|
||||||
|
RETURNS DECIMAL AS $$
|
||||||
|
DECLARE
|
||||||
|
from_factor DECIMAL;
|
||||||
|
to_factor DECIMAL;
|
||||||
|
from_type unit_type;
|
||||||
|
to_type unit_type;
|
||||||
|
base_quantity DECIMAL;
|
||||||
|
BEGIN
|
||||||
|
-- Get unit types and conversion factors
|
||||||
|
SELECT unit_type,
|
||||||
|
COALESCE(conversion_factor, 1.0) INTO from_type, from_factor
|
||||||
|
FROM units WHERE id = from_unit_id;
|
||||||
|
|
||||||
|
SELECT unit_type,
|
||||||
|
COALESCE(conversion_factor, 1.0) INTO to_type, to_factor
|
||||||
|
FROM units WHERE id = to_unit_id;
|
||||||
|
|
||||||
|
-- Check if units are compatible
|
||||||
|
IF from_type != to_type THEN
|
||||||
|
RAISE EXCEPTION 'Cannot convert between % and %', from_type, to_type;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- Convert to base unit, then to target unit
|
||||||
|
base_quantity := quantity * from_factor;
|
||||||
|
RETURN base_quantity / to_factor;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql IMMUTABLE;
|
||||||
|
|
||||||
|
-- Full-text search function for products
|
||||||
|
CREATE OR REPLACE FUNCTION search_products(search_query TEXT)
|
||||||
|
RETURNS TABLE (
|
||||||
|
id UUID,
|
||||||
|
barcode TEXT,
|
||||||
|
name TEXT,
|
||||||
|
brand TEXT,
|
||||||
|
rank REAL
|
||||||
|
) AS $$
|
||||||
|
BEGIN
|
||||||
|
RETURN QUERY
|
||||||
|
SELECT
|
||||||
|
p.id,
|
||||||
|
p.barcode,
|
||||||
|
p.name,
|
||||||
|
p.brand,
|
||||||
|
ts_rank(to_tsvector('english', p.name || ' ' || COALESCE(p.brand, '')),
|
||||||
|
plainto_tsquery('english', search_query)) AS rank
|
||||||
|
FROM products p
|
||||||
|
WHERE to_tsvector('english', p.name || ' ' || COALESCE(p.brand, ''))
|
||||||
|
@@ plainto_tsquery('english', search_query)
|
||||||
|
ORDER BY rank DESC
|
||||||
|
LIMIT 20;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
-- ======================
|
||||||
|
-- TRIGGERS
|
||||||
|
-- ======================
|
||||||
|
|
||||||
|
-- Auto-update timestamp triggers
|
||||||
|
CREATE TRIGGER update_items_updated_at
|
||||||
|
BEFORE UPDATE ON inventory_items
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE FUNCTION update_updated_at();
|
||||||
|
|
||||||
|
CREATE TRIGGER update_profiles_updated_at
|
||||||
|
BEFORE UPDATE ON user_profiles
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE FUNCTION update_updated_at();
|
||||||
|
|
||||||
|
-- ======================
|
||||||
|
-- COMMENTS
|
||||||
|
-- ======================
|
||||||
|
|
||||||
|
COMMENT ON TABLE inventory_items IS 'Current inventory items in the kitchen';
|
||||||
|
COMMENT ON TABLE products IS 'Cached product data from Open Food Facts';
|
||||||
|
COMMENT ON TABLE tags IS 'Flexible labeling system for organization';
|
||||||
|
COMMENT ON TABLE item_tags IS 'Many-to-many relationship between items and tags';
|
||||||
|
COMMENT ON TABLE units IS 'Measurement units with conversion support';
|
||||||
|
COMMENT ON TABLE user_profiles IS 'Additional user metadata and preferences';
|
||||||
|
|
||||||
|
COMMENT ON FUNCTION update_updated_at() IS 'Automatically updates the updated_at timestamp';
|
||||||
|
COMMENT ON FUNCTION convert_unit(DECIMAL, UUID, UUID) IS 'Converts quantity between compatible units';
|
||||||
|
COMMENT ON FUNCTION search_products(TEXT) IS 'Full-text search for products by name and brand';
|
||||||
180
supabase/migrations/002_rls_policies.sql
Normal file
180
supabase/migrations/002_rls_policies.sql
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
-- Migration: Row Level Security Policies
|
||||||
|
-- Created: 2026-02-09
|
||||||
|
-- Issue: #14
|
||||||
|
-- Description: Enables RLS and creates security policies for all tables
|
||||||
|
|
||||||
|
-- ======================
|
||||||
|
-- ENABLE RLS
|
||||||
|
-- ======================
|
||||||
|
|
||||||
|
ALTER TABLE inventory_items ENABLE ROW LEVEL SECURITY;
|
||||||
|
ALTER TABLE products ENABLE ROW LEVEL SECURITY;
|
||||||
|
ALTER TABLE tags ENABLE ROW LEVEL SECURITY;
|
||||||
|
ALTER TABLE item_tags ENABLE ROW LEVEL SECURITY;
|
||||||
|
ALTER TABLE units ENABLE ROW LEVEL SECURITY;
|
||||||
|
ALTER TABLE user_profiles ENABLE ROW LEVEL SECURITY;
|
||||||
|
|
||||||
|
-- ======================
|
||||||
|
-- INVENTORY_ITEMS POLICIES
|
||||||
|
-- ======================
|
||||||
|
|
||||||
|
-- Everyone can read (shared household inventory)
|
||||||
|
CREATE POLICY "inventory_items_select_all" ON inventory_items
|
||||||
|
FOR SELECT
|
||||||
|
USING (true);
|
||||||
|
|
||||||
|
-- Authenticated users can insert items
|
||||||
|
CREATE POLICY "inventory_items_insert_auth" ON inventory_items
|
||||||
|
FOR INSERT
|
||||||
|
WITH CHECK (auth.uid() IS NOT NULL);
|
||||||
|
|
||||||
|
-- Authenticated users can update any item
|
||||||
|
CREATE POLICY "inventory_items_update_auth" ON inventory_items
|
||||||
|
FOR UPDATE
|
||||||
|
USING (auth.uid() IS NOT NULL);
|
||||||
|
|
||||||
|
-- Authenticated users can delete any item
|
||||||
|
CREATE POLICY "inventory_items_delete_auth" ON inventory_items
|
||||||
|
FOR DELETE
|
||||||
|
USING (auth.uid() IS NOT NULL);
|
||||||
|
|
||||||
|
-- ======================
|
||||||
|
-- PRODUCTS POLICIES
|
||||||
|
-- ======================
|
||||||
|
|
||||||
|
-- Everyone can read cached products
|
||||||
|
CREATE POLICY "products_select_all" ON products
|
||||||
|
FOR SELECT
|
||||||
|
USING (true);
|
||||||
|
|
||||||
|
-- Only service role can write (via Edge Functions)
|
||||||
|
-- No user-level INSERT/UPDATE/DELETE policies
|
||||||
|
|
||||||
|
-- ======================
|
||||||
|
-- TAGS POLICIES
|
||||||
|
-- ======================
|
||||||
|
|
||||||
|
-- Everyone can read all tags
|
||||||
|
CREATE POLICY "tags_select_all" ON tags
|
||||||
|
FOR SELECT
|
||||||
|
USING (true);
|
||||||
|
|
||||||
|
-- Authenticated users can create tags
|
||||||
|
CREATE POLICY "tags_insert_auth" ON tags
|
||||||
|
FOR INSERT
|
||||||
|
WITH CHECK (auth.uid() IS NOT NULL);
|
||||||
|
|
||||||
|
-- Users can update their own tags OR system tags (created_by IS NULL)
|
||||||
|
CREATE POLICY "tags_update_own" ON tags
|
||||||
|
FOR UPDATE
|
||||||
|
USING (
|
||||||
|
created_by = auth.uid() OR created_by IS NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Users can only delete their own custom tags
|
||||||
|
CREATE POLICY "tags_delete_own" ON tags
|
||||||
|
FOR DELETE
|
||||||
|
USING (created_by = auth.uid());
|
||||||
|
|
||||||
|
-- ======================
|
||||||
|
-- ITEM_TAGS POLICIES
|
||||||
|
-- ======================
|
||||||
|
|
||||||
|
-- Everyone can read
|
||||||
|
CREATE POLICY "item_tags_select_all" ON item_tags
|
||||||
|
FOR SELECT
|
||||||
|
USING (true);
|
||||||
|
|
||||||
|
-- Authenticated users can add tags to items
|
||||||
|
CREATE POLICY "item_tags_insert_auth" ON item_tags
|
||||||
|
FOR INSERT
|
||||||
|
WITH CHECK (auth.uid() IS NOT NULL);
|
||||||
|
|
||||||
|
-- Authenticated users can remove tags from items
|
||||||
|
CREATE POLICY "item_tags_delete_auth" ON item_tags
|
||||||
|
FOR DELETE
|
||||||
|
USING (auth.uid() IS NOT NULL);
|
||||||
|
|
||||||
|
-- ======================
|
||||||
|
-- UNITS POLICIES
|
||||||
|
-- ======================
|
||||||
|
|
||||||
|
-- Everyone can read all units
|
||||||
|
CREATE POLICY "units_select_all" ON units
|
||||||
|
FOR SELECT
|
||||||
|
USING (true);
|
||||||
|
|
||||||
|
-- Authenticated users can create custom units (not default ones)
|
||||||
|
CREATE POLICY "units_insert_auth" ON units
|
||||||
|
FOR INSERT
|
||||||
|
WITH CHECK (
|
||||||
|
auth.uid() IS NOT NULL AND is_default = false
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Users can update their own custom units only
|
||||||
|
CREATE POLICY "units_update_own" ON units
|
||||||
|
FOR UPDATE
|
||||||
|
USING (
|
||||||
|
created_by = auth.uid() AND is_default = false
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Users can delete their own custom units only
|
||||||
|
CREATE POLICY "units_delete_own" ON units
|
||||||
|
FOR DELETE
|
||||||
|
USING (
|
||||||
|
created_by = auth.uid() AND is_default = false
|
||||||
|
);
|
||||||
|
|
||||||
|
-- ======================
|
||||||
|
-- USER_PROFILES POLICIES
|
||||||
|
-- ======================
|
||||||
|
|
||||||
|
-- Users can read all profiles (for display names, avatars)
|
||||||
|
CREATE POLICY "user_profiles_select_all" ON user_profiles
|
||||||
|
FOR SELECT
|
||||||
|
USING (true);
|
||||||
|
|
||||||
|
-- Users can insert their own profile
|
||||||
|
CREATE POLICY "user_profiles_insert_own" ON user_profiles
|
||||||
|
FOR INSERT
|
||||||
|
WITH CHECK (auth.uid() = id);
|
||||||
|
|
||||||
|
-- Users can update only their own profile
|
||||||
|
CREATE POLICY "user_profiles_update_own" ON user_profiles
|
||||||
|
FOR UPDATE
|
||||||
|
USING (auth.uid() = id);
|
||||||
|
|
||||||
|
-- Users can delete only their own profile
|
||||||
|
CREATE POLICY "user_profiles_delete_own" ON user_profiles
|
||||||
|
FOR DELETE
|
||||||
|
USING (auth.uid() = id);
|
||||||
|
|
||||||
|
-- ======================
|
||||||
|
-- COMMENTS
|
||||||
|
-- ======================
|
||||||
|
|
||||||
|
COMMENT ON POLICY "inventory_items_select_all" ON inventory_items IS 'Allow all users to view shared household inventory';
|
||||||
|
COMMENT ON POLICY "inventory_items_insert_auth" ON inventory_items IS 'Authenticated users can add items';
|
||||||
|
COMMENT ON POLICY "inventory_items_update_auth" ON inventory_items IS 'Authenticated users can update any item (shared inventory)';
|
||||||
|
COMMENT ON POLICY "inventory_items_delete_auth" ON inventory_items IS 'Authenticated users can delete any item';
|
||||||
|
|
||||||
|
COMMENT ON POLICY "products_select_all" ON products IS 'Allow all users to view cached product data';
|
||||||
|
|
||||||
|
COMMENT ON POLICY "tags_select_all" ON tags IS 'Allow all users to view tags (system and custom)';
|
||||||
|
COMMENT ON POLICY "tags_insert_auth" ON tags IS 'Authenticated users can create custom tags';
|
||||||
|
COMMENT ON POLICY "tags_update_own" ON tags IS 'Users can update their own tags or system tags';
|
||||||
|
COMMENT ON POLICY "tags_delete_own" ON tags IS 'Users can only delete their own custom tags';
|
||||||
|
|
||||||
|
COMMENT ON POLICY "item_tags_select_all" ON item_tags IS 'Allow all users to view item tag associations';
|
||||||
|
COMMENT ON POLICY "item_tags_insert_auth" ON item_tags IS 'Authenticated users can tag items';
|
||||||
|
COMMENT ON POLICY "item_tags_delete_auth" ON item_tags IS 'Authenticated users can remove tags from items';
|
||||||
|
|
||||||
|
COMMENT ON POLICY "units_select_all" ON units IS 'Allow all users to view all units';
|
||||||
|
COMMENT ON POLICY "units_insert_auth" ON units IS 'Authenticated users can create custom units';
|
||||||
|
COMMENT ON POLICY "units_update_own" ON units IS 'Users can only update their own custom units';
|
||||||
|
COMMENT ON POLICY "units_delete_own" ON units IS 'Users can only delete their own custom units';
|
||||||
|
|
||||||
|
COMMENT ON POLICY "user_profiles_select_all" ON user_profiles IS 'Allow users to view all profiles for display purposes';
|
||||||
|
COMMENT ON POLICY "user_profiles_insert_own" ON user_profiles IS 'Users can create their own profile';
|
||||||
|
COMMENT ON POLICY "user_profiles_update_own" ON user_profiles IS 'Users can only update their own profile';
|
||||||
|
COMMENT ON POLICY "user_profiles_delete_own" ON user_profiles IS 'Users can only delete their own profile';
|
||||||
Reference in New Issue
Block a user