Changes: - Add FAL_KEY and GEMINI_API_KEY to .env.example - Update picture-it to use ~/.config/opencode/.env (unified creds) - Remove shodh-memory skill (no longer used) - Remove alphaear-* skills (deprecated) - Remove thai-frontend-dev skill (replaced by website-creator) - Remove theme-factory skill - Add mql-developer skill (MQL5 trading) - Add ecommerce-astro skill (Astro e-commerce) - Add website-creator skill (Next.js + Payload CMS) - Update install script for new skills
422 lines
14 KiB
PL/PgSQL
422 lines
14 KiB
PL/PgSQL
-- =====================================================
|
|
-- E-commerce Database Schema for Supabase
|
|
-- Astro 6 + React + PaySo Multi-vendor Marketplace
|
|
-- =====================================================
|
|
|
|
-- Enable UUID extension
|
|
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
|
|
|
-- =====================================================
|
|
-- USERS & AUTHENTICATION
|
|
-- =====================================================
|
|
|
|
CREATE TABLE users (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
email TEXT UNIQUE NOT NULL,
|
|
password_hash TEXT NOT NULL,
|
|
name TEXT,
|
|
phone TEXT,
|
|
role TEXT DEFAULT 'customer' CHECK (role IN ('customer', 'vendor', 'admin')),
|
|
avatar_url TEXT,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
-- =====================================================
|
|
-- VENDOR PROFILES (Multi-vendor support)
|
|
-- =====================================================
|
|
|
|
CREATE TABLE vendor_profiles (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
user_id UUID REFERENCES users(id) ON DELETE CASCADE UNIQUE,
|
|
store_name TEXT NOT NULL,
|
|
store_slug TEXT UNIQUE NOT NULL,
|
|
store_description TEXT,
|
|
store_logo TEXT,
|
|
bank_account TEXT,
|
|
bank_name TEXT,
|
|
payout_status TEXT DEFAULT 'pending' CHECK (payout_status IN ('pending', 'approved', 'rejected')),
|
|
total_earnings DECIMAL(12,2) DEFAULT 0,
|
|
approved_at TIMESTAMPTZ,
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
-- =====================================================
|
|
-- CATEGORIES (Hierarchical)
|
|
-- =====================================================
|
|
|
|
CREATE TABLE categories (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
name TEXT NOT NULL,
|
|
slug TEXT UNIQUE NOT NULL,
|
|
description TEXT,
|
|
image_url TEXT,
|
|
parent_id UUID REFERENCES categories(id),
|
|
sort_order INT DEFAULT 0,
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
-- =====================================================
|
|
-- PRODUCTS
|
|
-- =====================================================
|
|
|
|
CREATE TABLE products (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
vendor_id UUID REFERENCES vendor_profiles(id) ON DELETE CASCADE,
|
|
category_id UUID REFERENCES categories(id),
|
|
name TEXT NOT NULL,
|
|
slug TEXT UNIQUE NOT NULL,
|
|
description TEXT,
|
|
price DECIMAL(12,2) NOT NULL,
|
|
compare_at_price DECIMAL(12,2),
|
|
cost_price DECIMAL(12,2),
|
|
sku TEXT UNIQUE,
|
|
barcode TEXT,
|
|
inventory INT DEFAULT 0,
|
|
low_stock_threshold INT DEFAULT 5,
|
|
track_inventory BOOLEAN DEFAULT TRUE,
|
|
allow_backorder BOOLEAN DEFAULT FALSE,
|
|
weight DECIMAL(8,2),
|
|
images JSONB DEFAULT '[]',
|
|
metadata JSONB DEFAULT '{}',
|
|
status TEXT DEFAULT 'draft' CHECK (status IN ('draft', 'active', 'archived')),
|
|
featured BOOLEAN DEFAULT FALSE,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
-- =====================================================
|
|
-- PRODUCT VARIANTS
|
|
-- =====================================================
|
|
|
|
CREATE TABLE product_variants (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
product_id UUID REFERENCES products(id) ON DELETE CASCADE,
|
|
name TEXT NOT NULL,
|
|
sku TEXT UNIQUE,
|
|
price DECIMAL(12,2),
|
|
inventory INT DEFAULT 0,
|
|
attributes JSONB DEFAULT '{}',
|
|
image_url TEXT,
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
-- =====================================================
|
|
-- REVIEWS
|
|
-- =====================================================
|
|
|
|
CREATE TABLE reviews (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
product_id UUID REFERENCES products(id) ON DELETE CASCADE,
|
|
user_id UUID REFERENCES users(id),
|
|
order_id UUID,
|
|
rating INT CHECK (rating >= 1 AND rating <= 5),
|
|
title TEXT,
|
|
comment TEXT,
|
|
images JSONB DEFAULT '[]',
|
|
verified_purchase BOOLEAN DEFAULT FALSE,
|
|
status TEXT DEFAULT 'pending' CHECK (status IN ('pending', 'approved', 'rejected')),
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
-- =====================================================
|
|
-- ORDERS
|
|
-- =====================================================
|
|
|
|
CREATE TABLE orders (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
order_number TEXT UNIQUE NOT NULL,
|
|
user_id UUID REFERENCES users(id),
|
|
vendor_id UUID REFERENCES vendor_profiles(id),
|
|
status TEXT DEFAULT 'pending' CHECK (status IN ('pending', 'confirmed', 'processing', 'shipped', 'delivered', 'cancelled', 'refunded')),
|
|
payment_status TEXT DEFAULT 'unpaid' CHECK (payment_status IN ('unpaid', 'paid', 'failed', 'refunded')),
|
|
subtotal DECIMAL(12,2) NOT NULL,
|
|
tax DECIMAL(12,2) DEFAULT 0,
|
|
shipping_cost DECIMAL(12,2) DEFAULT 0,
|
|
total DECIMAL(12,2) NOT NULL,
|
|
currency TEXT DEFAULT 'THB',
|
|
payment_method TEXT,
|
|
payment_provider TEXT,
|
|
payment_ref TEXT,
|
|
shipping_name TEXT,
|
|
shipping_phone TEXT,
|
|
shipping_address TEXT,
|
|
shipping_city TEXT,
|
|
shipping_postal TEXT,
|
|
shipping_country TEXT DEFAULT 'Thailand',
|
|
notes TEXT,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
-- =====================================================
|
|
-- ORDER ITEMS
|
|
-- =====================================================
|
|
|
|
CREATE TABLE order_items (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
order_id UUID REFERENCES orders(id) ON DELETE CASCADE,
|
|
product_id UUID REFERENCES products(id),
|
|
variant_id UUID REFERENCES product_variants(id),
|
|
vendor_id UUID REFERENCES vendor_profiles(id),
|
|
quantity INT NOT NULL,
|
|
unit_price DECIMAL(12,2) NOT NULL,
|
|
total_price DECIMAL(12,2) NOT NULL,
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
-- =====================================================
|
|
-- PAYMENTS
|
|
-- =====================================================
|
|
|
|
CREATE TABLE payments (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
order_id UUID REFERENCES orders(id),
|
|
user_id UUID REFERENCES users(id),
|
|
provider TEXT NOT NULL,
|
|
provider_ref TEXT,
|
|
amount DECIMAL(12,2) NOT NULL,
|
|
currency TEXT DEFAULT 'THB',
|
|
status TEXT DEFAULT 'pending' CHECK (status IN ('pending', 'completed', 'failed', 'refunded')),
|
|
payment_data JSONB,
|
|
paid_at TIMESTAMPTZ,
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
-- =====================================================
|
|
-- VENDOR PAYOUTS
|
|
-- =====================================================
|
|
|
|
CREATE TABLE vendor_payouts (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
vendor_id UUID REFERENCES vendor_profiles(id),
|
|
amount DECIMAL(12,2) NOT NULL,
|
|
status TEXT DEFAULT 'pending' CHECK (status IN ('pending', 'processing', 'completed', 'failed')),
|
|
bank_account TEXT,
|
|
bank_name TEXT,
|
|
notes TEXT,
|
|
processed_at TIMESTAMPTZ,
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
-- =====================================================
|
|
-- SHOPPING CARTS
|
|
-- =====================================================
|
|
|
|
CREATE TABLE carts (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
user_id UUID REFERENCES users(id) ON DELETE CASCADE UNIQUE,
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE TABLE cart_items (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
cart_id UUID REFERENCES carts(id) ON DELETE CASCADE,
|
|
product_id UUID REFERENCES products(id),
|
|
variant_id UUID REFERENCES product_variants(id),
|
|
quantity INT DEFAULT 1,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
UNIQUE(cart_id, product_id, variant_id)
|
|
);
|
|
|
|
-- =====================================================
|
|
-- INDEXES FOR PERFORMANCE
|
|
-- =====================================================
|
|
|
|
-- Products
|
|
CREATE INDEX idx_products_vendor ON products(vendor_id);
|
|
CREATE INDEX idx_products_category ON products(category_id);
|
|
CREATE INDEX idx_products_slug ON products(slug);
|
|
CREATE INDEX idx_products_status ON products(status);
|
|
CREATE INDEX idx_products_featured ON products(featured) WHERE featured = TRUE;
|
|
CREATE INDEX idx_products_inventory ON products(inventory);
|
|
|
|
-- Orders
|
|
CREATE INDEX idx_orders_user ON orders(user_id);
|
|
CREATE INDEX idx_orders_vendor ON orders(vendor_id);
|
|
CREATE INDEX idx_orders_number ON orders(order_number);
|
|
CREATE INDEX idx_orders_status ON orders(status);
|
|
CREATE INDEX idx_orders_payment_status ON orders(payment_status);
|
|
CREATE INDEX idx_orders_created ON orders(created_at DESC);
|
|
|
|
-- Order Items
|
|
CREATE INDEX idx_order_items_order ON order_items(order_id);
|
|
CREATE INDEX idx_order_items_product ON order_items(product_id);
|
|
CREATE INDEX idx_order_items_vendor ON order_items(vendor_id);
|
|
|
|
-- Reviews
|
|
CREATE INDEX idx_reviews_product ON reviews(product_id);
|
|
CREATE INDEX idx_reviews_user ON reviews(user_id);
|
|
CREATE INDEX idx_reviews_product_rating ON reviews(product_id, rating);
|
|
|
|
-- Cart
|
|
CREATE INDEX idx_cart_items_cart ON cart_items(cart_id);
|
|
|
|
-- Payments
|
|
CREATE INDEX idx_payments_order ON payments(order_id);
|
|
CREATE INDEX idx_payments_status ON payments(status);
|
|
|
|
-- Vendor
|
|
CREATE INDEX idx_vendor_profiles_user ON vendor_profiles(user_id);
|
|
CREATE INDEX idx_vendor_profiles_slug ON vendor_profiles(store_slug);
|
|
|
|
-- Categories
|
|
CREATE INDEX idx_categories_parent ON categories(parent_id);
|
|
|
|
-- =====================================================
|
|
-- ROW LEVEL SECURITY (RLS)
|
|
-- =====================================================
|
|
|
|
-- Enable RLS on all tables
|
|
ALTER TABLE users ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE vendor_profiles ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE categories ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE products ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE product_variants ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE reviews ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE order_items ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE payments ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE vendor_payouts ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE carts ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE cart_items ENABLE ROW LEVEL SECURITY;
|
|
|
|
-- Users: Users can read their own profile
|
|
CREATE POLICY "Users can view own profile" ON users
|
|
FOR SELECT USING (auth.uid() = id);
|
|
|
|
CREATE POLICY "Users can update own profile" ON users
|
|
FOR UPDATE USING (auth.uid() = id);
|
|
|
|
-- Products: Anyone can view active products
|
|
CREATE POLICY "Anyone can view active products" ON products
|
|
FOR SELECT USING (status = 'active');
|
|
|
|
CREATE POLICY "Vendors can manage own products" ON products
|
|
FOR ALL USING (
|
|
vendor_id IN (
|
|
SELECT id FROM vendor_profiles WHERE user_id = auth.uid()
|
|
)
|
|
);
|
|
|
|
-- Categories: Anyone can view categories
|
|
CREATE POLICY "Anyone can view categories" ON categories
|
|
FOR SELECT USING (true);
|
|
|
|
-- Reviews: Anyone can view approved reviews
|
|
CREATE POLICY "Anyone can view approved reviews" ON reviews
|
|
FOR SELECT USING (status = 'approved');
|
|
|
|
-- Orders: Users can view their own orders
|
|
CREATE POLICY "Users can view own orders" ON orders
|
|
FOR SELECT USING (auth.uid() = user_id);
|
|
|
|
CREATE POLICY "Vendors can view their orders" ON orders
|
|
FOR SELECT USING (
|
|
vendor_id IN (
|
|
SELECT id FROM vendor_profiles WHERE user_id = auth.uid()
|
|
)
|
|
);
|
|
|
|
-- Carts: Users can manage their own cart
|
|
CREATE POLICY "Users can manage own cart" ON carts
|
|
FOR ALL USING (user_id = auth.uid());
|
|
|
|
CREATE POLICY "Users can manage own cart items" ON cart_items
|
|
FOR ALL USING (
|
|
cart_id IN (SELECT id FROM carts WHERE user_id = auth.uid())
|
|
);
|
|
|
|
-- =====================================================
|
|
-- FUNCTIONS & TRIGGERS
|
|
-- =====================================================
|
|
|
|
-- Auto-update updated_at timestamp
|
|
CREATE OR REPLACE FUNCTION update_updated_at_column()
|
|
RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
NEW.updated_at = NOW();
|
|
RETURN NEW;
|
|
END;
|
|
$$ language 'plpgsql';
|
|
|
|
-- Apply to tables with updated_at
|
|
CREATE TRIGGER update_users_updated_at
|
|
BEFORE UPDATE ON users
|
|
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
|
|
|
CREATE TRIGGER update_products_updated_at
|
|
BEFORE UPDATE ON products
|
|
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
|
|
|
CREATE TRIGGER update_orders_updated_at
|
|
BEFORE UPDATE ON orders
|
|
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
|
|
|
-- Function to generate order number
|
|
CREATE OR REPLACE FUNCTION generate_order_number()
|
|
RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
NEW.order_number = 'ORD-' || TO_CHAR(NOW(), 'YYYYMMDD') || '-' || UPPER(SUBSTRING(NEW.id::text, 1, 8));
|
|
RETURN NEW;
|
|
END;
|
|
$$ language 'plpgsql';
|
|
|
|
CREATE TRIGGER generate_order_number_trigger
|
|
BEFORE INSERT ON orders
|
|
FOR EACH ROW EXECUTE FUNCTION generate_order_number();
|
|
|
|
-- Function to update product inventory on order
|
|
CREATE OR REPLACE FUNCTION update_inventory_on_order()
|
|
RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
IF TG_OP = 'INSERT' THEN
|
|
UPDATE products
|
|
SET inventory = inventory - NEW.quantity
|
|
WHERE id = NEW.product_id AND track_inventory = TRUE;
|
|
ELSIF TG_OP = 'DELETE' THEN
|
|
UPDATE products
|
|
SET inventory = inventory + OLD.quantity
|
|
WHERE id = OLD.product_id AND track_inventory = TRUE;
|
|
END IF;
|
|
RETURN NULL;
|
|
END;
|
|
$$ language 'plpgsql';
|
|
|
|
CREATE TRIGGER update_inventory_trigger
|
|
AFTER INSERT OR DELETE ON order_items
|
|
FOR EACH ROW EXECUTE FUNCTION update_inventory_on_order();
|
|
|
|
-- Function to update vendor earnings on payment
|
|
CREATE OR REPLACE FUNCTION update_vendor_earnings()
|
|
RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
IF NEW.status = 'completed' THEN
|
|
UPDATE vendor_profiles
|
|
SET total_earnings = total_earnings + NEW.amount
|
|
WHERE vendor_id IN (
|
|
SELECT vendor_id FROM orders WHERE id = NEW.order_id
|
|
);
|
|
END IF;
|
|
RETURN NEW;
|
|
END;
|
|
$$ language 'plpgsql';
|
|
|
|
CREATE TRIGGER update_vendor_earnings_trigger
|
|
AFTER UPDATE OF status ON payments
|
|
FOR EACH ROW EXECUTE FUNCTION update_vendor_earnings();
|
|
|
|
-- =====================================================
|
|
-- SEED DATA (Optional sample categories)
|
|
-- =====================================================
|
|
|
|
-- Uncomment to add sample categories
|
|
/*
|
|
INSERT INTO categories (name, slug, description, sort_order) VALUES
|
|
('Electronics', 'electronics', 'Electronic devices and accessories', 1),
|
|
('Clothing', 'clothing', 'Fashion and apparel', 2),
|
|
('Home & Garden', 'home-garden', 'Home improvement and garden', 3),
|
|
('Sports', 'sports', 'Sports and outdoor equipment', 4),
|
|
('Books', 'books', 'Books and media', 5);
|
|
*/
|