mirror of
https://github.com/zooai/computer.git
synced 2026-10-30 14:50:20 +00:00
Forked from hanzo.computer, fully rebranded for Zoo Labs Foundation. Vite + React + Tailwind. Deploys to GitHub Pages.
326 lines
13 KiB
PL/PgSQL
326 lines
13 KiB
PL/PgSQL
-- Hanzo.Computer Database Schema for Supabase
|
|
-- Run this in Supabase SQL Editor to create all tables
|
|
|
|
-- Enable UUID extension
|
|
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
|
|
|
-- Users table (extends Supabase auth.users)
|
|
CREATE TABLE IF NOT EXISTS public.users (
|
|
id UUID PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE,
|
|
email VARCHAR(255) UNIQUE NOT NULL,
|
|
name VARCHAR(255) NOT NULL,
|
|
company VARCHAR(255),
|
|
stripe_customer_id VARCHAR(255),
|
|
role VARCHAR(50) DEFAULT 'customer', -- 'customer', 'admin'
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
);
|
|
|
|
-- Request for Quotes (RFQs)
|
|
CREATE TABLE IF NOT EXISTS public.rfqs (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
user_id UUID REFERENCES public.users(id) ON DELETE SET NULL,
|
|
company VARCHAR(255) NOT NULL,
|
|
email VARCHAR(255) NOT NULL,
|
|
phone VARCHAR(50),
|
|
gpu_type VARCHAR(100) NOT NULL, -- 'b300', 'b200', 'h200', 'h100', 'custom'
|
|
quantity INTEGER NOT NULL,
|
|
duration_months INTEGER, -- null for one-time purchase
|
|
use_case TEXT NOT NULL,
|
|
budget_range VARCHAR(100),
|
|
additional_requirements TEXT,
|
|
status VARCHAR(50) DEFAULT 'pending', -- 'pending', 'reviewing', 'quoted', 'accepted', 'rejected'
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
);
|
|
|
|
-- Cluster Requests
|
|
CREATE TABLE IF NOT EXISTS public.cluster_requests (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
user_id UUID REFERENCES public.users(id) ON DELETE SET NULL,
|
|
first_name VARCHAR(255) NOT NULL,
|
|
last_name VARCHAR(255) NOT NULL,
|
|
email VARCHAR(255) NOT NULL,
|
|
company VARCHAR(255) NOT NULL,
|
|
cluster_requirements TEXT NOT NULL,
|
|
number_of_gpus VARCHAR(50) NOT NULL,
|
|
rental_duration VARCHAR(50) NOT NULL,
|
|
project_description TEXT NOT NULL,
|
|
hear_about_us VARCHAR(100) NOT NULL,
|
|
status VARCHAR(50) DEFAULT 'pending', -- 'pending', 'reviewing', 'quoted', 'accepted', 'rejected'
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
);
|
|
|
|
-- Quotes (sent by admin in response to RFQ or cluster request)
|
|
CREATE TABLE IF NOT EXISTS public.quotes (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
rfq_id UUID REFERENCES public.rfqs(id) ON DELETE SET NULL,
|
|
cluster_request_id UUID REFERENCES public.cluster_requests(id) ON DELETE SET NULL,
|
|
user_id UUID REFERENCES public.users(id) ON DELETE SET NULL,
|
|
quote_number VARCHAR(100) UNIQUE NOT NULL, -- 'Q-2025-001'
|
|
items JSONB NOT NULL, -- [{name, description, quantity, unit_price, total}]
|
|
subtotal DECIMAL(10,2) NOT NULL,
|
|
tax DECIMAL(10,2) DEFAULT 0,
|
|
total DECIMAL(10,2) NOT NULL,
|
|
payment_terms VARCHAR(255), -- 'Monthly subscription', 'Annual prepaid', etc.
|
|
valid_until DATE,
|
|
notes TEXT,
|
|
status VARCHAR(50) DEFAULT 'sent', -- 'sent', 'viewed', 'accepted', 'expired', 'rejected'
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
accepted_at TIMESTAMP WITH TIME ZONE
|
|
);
|
|
|
|
-- Orders (from accepted quotes or direct purchases)
|
|
CREATE TABLE IF NOT EXISTS public.orders (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
order_number VARCHAR(100) UNIQUE NOT NULL, -- 'ORD-2025-001'
|
|
user_id UUID REFERENCES public.users(id) ON DELETE SET NULL,
|
|
quote_id UUID REFERENCES public.quotes(id) ON DELETE SET NULL,
|
|
stripe_payment_intent_id VARCHAR(255),
|
|
stripe_subscription_id VARCHAR(255), -- for recurring billing
|
|
items JSONB NOT NULL,
|
|
subtotal DECIMAL(10,2) NOT NULL,
|
|
tax DECIMAL(10,2) DEFAULT 0,
|
|
total DECIMAL(10,2) NOT NULL,
|
|
status VARCHAR(50) DEFAULT 'pending', -- 'pending', 'paid', 'provisioning', 'active', 'cancelled'
|
|
payment_method VARCHAR(50), -- 'stripe_card', 'stripe_ach', 'wire_transfer'
|
|
billing_cycle VARCHAR(50), -- 'one_time', 'monthly', '6_month', 'annual'
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
paid_at TIMESTAMP WITH TIME ZONE,
|
|
provisioned_at TIMESTAMP WITH TIME ZONE
|
|
);
|
|
|
|
-- Subscriptions (for GPU leasing)
|
|
CREATE TABLE IF NOT EXISTS public.subscriptions (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
user_id UUID REFERENCES public.users(id) ON DELETE SET NULL,
|
|
order_id UUID REFERENCES public.orders(id) ON DELETE SET NULL,
|
|
stripe_subscription_id VARCHAR(255) UNIQUE NOT NULL,
|
|
stripe_product_id VARCHAR(255),
|
|
stripe_price_id VARCHAR(255),
|
|
gpu_type VARCHAR(100),
|
|
quantity INTEGER,
|
|
status VARCHAR(50) DEFAULT 'active', -- 'active', 'cancelled', 'past_due', 'paused'
|
|
current_period_start TIMESTAMP WITH TIME ZONE,
|
|
current_period_end TIMESTAMP WITH TIME ZONE,
|
|
cancel_at_period_end BOOLEAN DEFAULT FALSE,
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
cancelled_at TIMESTAMP WITH TIME ZONE
|
|
);
|
|
|
|
-- Invoices (generated by Stripe or manually)
|
|
CREATE TABLE IF NOT EXISTS public.invoices (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
invoice_number VARCHAR(100) UNIQUE NOT NULL, -- 'INV-2025-001'
|
|
user_id UUID REFERENCES public.users(id) ON DELETE SET NULL,
|
|
order_id UUID REFERENCES public.orders(id) ON DELETE SET NULL,
|
|
subscription_id UUID REFERENCES public.subscriptions(id) ON DELETE SET NULL,
|
|
stripe_invoice_id VARCHAR(255),
|
|
amount_due DECIMAL(10,2) NOT NULL,
|
|
amount_paid DECIMAL(10,2) DEFAULT 0,
|
|
status VARCHAR(50) DEFAULT 'draft', -- 'draft', 'open', 'paid', 'void', 'uncollectible'
|
|
due_date DATE,
|
|
paid_at TIMESTAMP WITH TIME ZONE,
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
);
|
|
|
|
-- GPU Reservations (what customer actually gets)
|
|
CREATE TABLE IF NOT EXISTS public.gpu_reservations (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
user_id UUID REFERENCES public.users(id) ON DELETE SET NULL,
|
|
order_id UUID REFERENCES public.orders(id) ON DELETE SET NULL,
|
|
subscription_id UUID REFERENCES public.subscriptions(id) ON DELETE SET NULL,
|
|
gpu_type VARCHAR(100),
|
|
quantity INTEGER,
|
|
instance_ids JSONB, -- array of actual server/instance IDs
|
|
access_credentials JSONB, -- encrypted connection details
|
|
status VARCHAR(50) DEFAULT 'provisioning', -- 'provisioning', 'active', 'suspended', 'terminated'
|
|
provisioned_at TIMESTAMP WITH TIME ZONE,
|
|
expires_at TIMESTAMP WITH TIME ZONE,
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
);
|
|
|
|
-- Usage Tracking
|
|
CREATE TABLE IF NOT EXISTS public.usage_records (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
user_id UUID REFERENCES public.users(id) ON DELETE SET NULL,
|
|
gpu_reservation_id UUID REFERENCES public.gpu_reservations(id) ON DELETE CASCADE,
|
|
hours_used DECIMAL(10,2),
|
|
compute_units DECIMAL(10,2),
|
|
recorded_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
);
|
|
|
|
-- Admin Actions Log
|
|
CREATE TABLE IF NOT EXISTS public.admin_actions (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
admin_id UUID REFERENCES public.users(id) ON DELETE SET NULL,
|
|
action VARCHAR(100) NOT NULL, -- 'quote_sent', 'order_approved', 'subscription_cancelled', etc.
|
|
entity_type VARCHAR(50), -- 'rfq', 'quote', 'order', 'subscription'
|
|
entity_id UUID,
|
|
notes TEXT,
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
|
);
|
|
|
|
-- Create indexes for better query performance
|
|
CREATE INDEX IF NOT EXISTS idx_rfqs_user_id ON public.rfqs(user_id);
|
|
CREATE INDEX IF NOT EXISTS idx_rfqs_status ON public.rfqs(status);
|
|
CREATE INDEX IF NOT EXISTS idx_rfqs_created_at ON public.rfqs(created_at DESC);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_cluster_requests_user_id ON public.cluster_requests(user_id);
|
|
CREATE INDEX IF NOT EXISTS idx_cluster_requests_status ON public.cluster_requests(status);
|
|
CREATE INDEX IF NOT EXISTS idx_cluster_requests_created_at ON public.cluster_requests(created_at DESC);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_quotes_user_id ON public.quotes(user_id);
|
|
CREATE INDEX IF NOT EXISTS idx_quotes_rfq_id ON public.quotes(rfq_id);
|
|
CREATE INDEX IF NOT EXISTS idx_quotes_cluster_request_id ON public.quotes(cluster_request_id);
|
|
CREATE INDEX IF NOT EXISTS idx_quotes_status ON public.quotes(status);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_orders_user_id ON public.orders(user_id);
|
|
CREATE INDEX IF NOT EXISTS idx_orders_status ON public.orders(status);
|
|
CREATE INDEX IF NOT EXISTS idx_orders_created_at ON public.orders(created_at DESC);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_subscriptions_user_id ON public.subscriptions(user_id);
|
|
CREATE INDEX IF NOT EXISTS idx_subscriptions_status ON public.subscriptions(status);
|
|
CREATE INDEX IF NOT EXISTS idx_subscriptions_stripe_subscription_id ON public.subscriptions(stripe_subscription_id);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_gpu_reservations_user_id ON public.gpu_reservations(user_id);
|
|
CREATE INDEX IF NOT EXISTS idx_gpu_reservations_status ON public.gpu_reservations(status);
|
|
|
|
-- Row Level Security (RLS) Policies
|
|
|
|
-- Enable RLS on all tables
|
|
ALTER TABLE public.users ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE public.rfqs ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE public.cluster_requests ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE public.quotes ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE public.orders ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE public.subscriptions ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE public.invoices ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE public.gpu_reservations ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE public.usage_records ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE public.admin_actions ENABLE ROW LEVEL SECURITY;
|
|
|
|
-- Users: Users can read/update their own profile
|
|
CREATE POLICY "Users can view own profile" ON public.users
|
|
FOR SELECT USING (auth.uid() = id);
|
|
|
|
CREATE POLICY "Users can update own profile" ON public.users
|
|
FOR UPDATE USING (auth.uid() = id);
|
|
|
|
-- RFQs: Users can create and view their own RFQs, admins can view all
|
|
CREATE POLICY "Anyone can create RFQ" ON public.rfqs
|
|
FOR INSERT WITH CHECK (true);
|
|
|
|
CREATE POLICY "Users can view own RFQs" ON public.rfqs
|
|
FOR SELECT USING (
|
|
auth.uid() = user_id OR
|
|
EXISTS (SELECT 1 FROM public.users WHERE id = auth.uid() AND role = 'admin')
|
|
);
|
|
|
|
-- Cluster Requests: Same as RFQs
|
|
CREATE POLICY "Anyone can create cluster request" ON public.cluster_requests
|
|
FOR INSERT WITH CHECK (true);
|
|
|
|
CREATE POLICY "Users can view own cluster requests" ON public.cluster_requests
|
|
FOR SELECT USING (
|
|
auth.uid() = user_id OR
|
|
EXISTS (SELECT 1 FROM public.users WHERE id = auth.uid() AND role = 'admin')
|
|
);
|
|
|
|
-- Quotes: Users can view their own quotes, admins can manage all
|
|
CREATE POLICY "Users can view own quotes" ON public.quotes
|
|
FOR SELECT USING (
|
|
auth.uid() = user_id OR
|
|
EXISTS (SELECT 1 FROM public.users WHERE id = auth.uid() AND role = 'admin')
|
|
);
|
|
|
|
CREATE POLICY "Admins can create quotes" ON public.quotes
|
|
FOR INSERT WITH CHECK (
|
|
EXISTS (SELECT 1 FROM public.users WHERE id = auth.uid() AND role = 'admin')
|
|
);
|
|
|
|
CREATE POLICY "Admins can update quotes" ON public.quotes
|
|
FOR UPDATE USING (
|
|
EXISTS (SELECT 1 FROM public.users WHERE id = auth.uid() AND role = 'admin')
|
|
);
|
|
|
|
-- Orders: Users can view their own orders, admins can manage all
|
|
CREATE POLICY "Users can view own orders" ON public.orders
|
|
FOR SELECT USING (
|
|
auth.uid() = user_id OR
|
|
EXISTS (SELECT 1 FROM public.users WHERE id = auth.uid() AND role = 'admin')
|
|
);
|
|
|
|
CREATE POLICY "Users can create orders" ON public.orders
|
|
FOR INSERT WITH CHECK (auth.uid() = user_id);
|
|
|
|
CREATE POLICY "Admins can update orders" ON public.orders
|
|
FOR UPDATE USING (
|
|
EXISTS (SELECT 1 FROM public.users WHERE id = auth.uid() AND role = 'admin')
|
|
);
|
|
|
|
-- Subscriptions: Users can view their own, admins can manage all
|
|
CREATE POLICY "Users can view own subscriptions" ON public.subscriptions
|
|
FOR SELECT USING (
|
|
auth.uid() = user_id OR
|
|
EXISTS (SELECT 1 FROM public.users WHERE id = auth.uid() AND role = 'admin')
|
|
);
|
|
|
|
CREATE POLICY "Admins can manage subscriptions" ON public.subscriptions
|
|
FOR ALL USING (
|
|
EXISTS (SELECT 1 FROM public.users WHERE id = auth.uid() AND role = 'admin')
|
|
);
|
|
|
|
-- GPU Reservations: Users can view their own
|
|
CREATE POLICY "Users can view own GPU reservations" ON public.gpu_reservations
|
|
FOR SELECT USING (
|
|
auth.uid() = user_id OR
|
|
EXISTS (SELECT 1 FROM public.users WHERE id = auth.uid() AND role = 'admin')
|
|
);
|
|
|
|
CREATE POLICY "Admins can manage GPU reservations" ON public.gpu_reservations
|
|
FOR ALL USING (
|
|
EXISTS (SELECT 1 FROM public.users WHERE id = auth.uid() AND role = 'admin')
|
|
);
|
|
|
|
-- Usage Records: Users can view their own
|
|
CREATE POLICY "Users can view own usage" ON public.usage_records
|
|
FOR SELECT USING (
|
|
auth.uid() = user_id OR
|
|
EXISTS (SELECT 1 FROM public.users WHERE id = auth.uid() AND role = 'admin')
|
|
);
|
|
|
|
-- Admin Actions: Only admins can view
|
|
CREATE POLICY "Admins can view admin actions" ON public.admin_actions
|
|
FOR SELECT USING (
|
|
EXISTS (SELECT 1 FROM public.users WHERE id = auth.uid() AND role = 'admin')
|
|
);
|
|
|
|
CREATE POLICY "Admins can create admin actions" ON public.admin_actions
|
|
FOR INSERT WITH CHECK (
|
|
EXISTS (SELECT 1 FROM public.users WHERE id = auth.uid() AND role = 'admin')
|
|
);
|
|
|
|
-- Functions for automatic timestamp updates
|
|
CREATE OR REPLACE FUNCTION public.handle_updated_at()
|
|
RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
NEW.updated_at = NOW();
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
-- Triggers for updated_at
|
|
CREATE TRIGGER set_updated_at_users
|
|
BEFORE UPDATE ON public.users
|
|
FOR EACH ROW EXECUTE FUNCTION public.handle_updated_at();
|
|
|
|
CREATE TRIGGER set_updated_at_rfqs
|
|
BEFORE UPDATE ON public.rfqs
|
|
FOR EACH ROW EXECUTE FUNCTION public.handle_updated_at();
|
|
|
|
CREATE TRIGGER set_updated_at_cluster_requests
|
|
BEFORE UPDATE ON public.cluster_requests
|
|
FOR EACH ROW EXECUTE FUNCTION public.handle_updated_at();
|