Skip to content

Commit 43a43d5

Browse files
chuckcarpenterRobbieTheWagner
authored andcommitted
Add Supabase database schema with RLS policies
- Define palettes and colors tables with proper constraints - Add Row Level Security policies for user data isolation - Include indexes for query performance - Add helper functions for data migration from Orbit.js
1 parent 7d3fdd8 commit 43a43d5

1 file changed

Lines changed: 259 additions & 0 deletions

File tree

supabase-schema.sql

Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
-- ============================================
2+
-- Swach Supabase Database Schema
3+
-- ============================================
4+
5+
-- Enable UUID extension for generating IDs
6+
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
7+
8+
-- ============================================
9+
-- Palettes Table
10+
-- ============================================
11+
CREATE TABLE IF NOT EXISTS palettes (
12+
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
13+
user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE,
14+
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
15+
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
16+
name TEXT,
17+
is_color_history BOOLEAN DEFAULT FALSE,
18+
is_favorite BOOLEAN DEFAULT FALSE,
19+
is_locked BOOLEAN DEFAULT FALSE,
20+
selected_color_index INTEGER DEFAULT 0,
21+
color_order JSONB DEFAULT '[]'::jsonb,
22+
23+
-- Constraints
24+
CONSTRAINT palettes_user_not_null CHECK (user_id IS NOT NULL),
25+
CONSTRAINT palettes_single_color_history_per_user UNIQUE (user_id, is_color_history)
26+
DEFERRABLE INITIALLY DEFERRED,
27+
CONSTRAINT palettes_name_length CHECK (char_length(name) <= 255)
28+
);
29+
30+
-- ============================================
31+
-- Colors Table
32+
-- ============================================
33+
CREATE TABLE IF NOT EXISTS colors (
34+
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
35+
user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE,
36+
palette_id UUID REFERENCES palettes(id) ON DELETE CASCADE,
37+
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
38+
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
39+
name TEXT,
40+
r INTEGER NOT NULL CHECK (r >= 0 AND r <= 255),
41+
g INTEGER NOT NULL CHECK (g >= 0 AND g <= 255),
42+
b INTEGER NOT NULL CHECK (b >= 0 AND b <= 255),
43+
a REAL DEFAULT 1.0 CHECK (a >= 0.0 AND a <= 1.0),
44+
45+
-- Constraints
46+
CONSTRAINT colors_user_not_null CHECK (user_id IS NOT NULL),
47+
CONSTRAINT colors_palette_not_null CHECK (palette_id IS NOT NULL),
48+
CONSTRAINT colors_name_length CHECK (char_length(name) <= 255)
49+
);
50+
51+
-- ============================================
52+
-- Indexes for Performance
53+
-- ============================================
54+
55+
-- Palettes indexes
56+
CREATE INDEX IF NOT EXISTS idx_palettes_user_id ON palettes(user_id);
57+
CREATE INDEX IF NOT EXISTS idx_palettes_created_at ON palettes(created_at);
58+
CREATE INDEX IF NOT EXISTS idx_palettes_is_favorite ON palettes(is_favorite) WHERE is_favorite = TRUE;
59+
CREATE INDEX IF NOT EXISTS idx_palettes_is_color_history ON palettes(is_color_history) WHERE is_color_history = TRUE;
60+
61+
-- Colors indexes
62+
CREATE INDEX IF NOT EXISTS idx_colors_user_id ON colors(user_id);
63+
CREATE INDEX IF NOT EXISTS idx_colors_palette_id ON colors(palette_id);
64+
CREATE INDEX IF NOT EXISTS idx_colors_created_at ON colors(created_at);
65+
66+
-- GIN indexes for JSONB columns
67+
CREATE INDEX IF NOT EXISTS idx_palettes_color_order ON palettes USING GIN(color_order);
68+
69+
-- ============================================
70+
-- Row Level Security (RLS) Policies
71+
-- ============================================
72+
73+
-- Enable RLS on all tables
74+
ALTER TABLE palettes ENABLE ROW LEVEL SECURITY;
75+
ALTER TABLE colors ENABLE ROW LEVEL SECURITY;
76+
77+
-- Policy: Users can view their own palettes
78+
CREATE POLICY "Users can view own palettes" ON palettes
79+
FOR SELECT USING (auth.uid() = user_id);
80+
81+
-- Policy: Users can insert their own palettes
82+
CREATE POLICY "Users can insert own palettes" ON palettes
83+
FOR INSERT WITH CHECK (auth.uid() = user_id);
84+
85+
-- Policy: Users can update their own palettes
86+
CREATE POLICY "Users can update own palettes" ON palettes
87+
FOR UPDATE USING (auth.uid() = user_id);
88+
89+
-- Policy: Users can delete their own palettes
90+
CREATE POLICY "Users can delete own palettes" ON palettes
91+
FOR DELETE USING (auth.uid() = user_id);
92+
93+
-- Policy: Users can view their own colors
94+
CREATE POLICY "Users can view own colors" ON colors
95+
FOR SELECT USING (auth.uid() = user_id);
96+
97+
-- Policy: Users can insert their own colors
98+
CREATE POLICY "Users can insert own colors" ON colors
99+
FOR INSERT WITH CHECK (auth.uid() = user_id);
100+
101+
-- Policy: Users can update their own colors
102+
CREATE POLICY "Users can update own colors" ON colors
103+
FOR UPDATE USING (auth.uid() = user_id);
104+
105+
-- Policy: Users can delete their own colors
106+
CREATE POLICY "Users can delete own colors" ON colors
107+
FOR DELETE USING (auth.uid() = user_id);
108+
109+
-- ============================================
110+
-- Functions for Data Management
111+
-- ============================================
112+
113+
-- Function to automatically update updated_at timestamp
114+
CREATE OR REPLACE FUNCTION update_updated_at_column()
115+
RETURNS TRIGGER AS $$
116+
BEGIN
117+
NEW.updated_at = NOW();
118+
RETURN NEW;
119+
END;
120+
$$ language 'plpgsql';
121+
122+
-- Triggers to auto-update updated_at
123+
CREATE TRIGGER update_palettes_updated_at
124+
BEFORE UPDATE ON palettes
125+
FOR EACH ROW EXECUTE PROCEDURE update_updated_at_column();
126+
127+
CREATE TRIGGER update_colors_updated_at
128+
BEFORE UPDATE ON colors
129+
FOR EACH ROW EXECUTE PROCEDURE update_updated_at_column();
130+
131+
-- ============================================
132+
-- Sample Data for Testing
133+
-- ============================================
134+
135+
-- Function to create sample color history palette for new users
136+
CREATE OR REPLACE FUNCTION create_sample_color_history_for_user(user_uuid UUID)
137+
RETURNS UUID AS $$
138+
DECLARE
139+
palette_uuid UUID;
140+
color_uuid1 UUID;
141+
color_uuid2 UUID;
142+
color_uuid3 UUID;
143+
BEGIN
144+
-- Create color history palette
145+
INSERT INTO palettes (user_id, name, is_color_history, selected_color_index, color_order)
146+
VALUES (
147+
user_uuid,
148+
'Color History',
149+
TRUE,
150+
0,
151+
'[]'::jsonb
152+
)
153+
RETURNING id INTO palette_uuid;
154+
155+
-- Create sample colors
156+
INSERT INTO colors (user_id, palette_id, name, r, g, b, a)
157+
VALUES
158+
(user_uuid, palette_uuid, 'Pure Black', 0, 0, 0, 1.0),
159+
(user_uuid, palette_uuid, 'Pure White', 255, 255, 255, 1.0),
160+
(user_uuid, palette_uuid, 'Pure Red', 255, 0, 0, 1.0)
161+
RETURNING id INTO color_uuid1, color_uuid2, color_uuid3;
162+
163+
-- Update color_order with the color references
164+
UPDATE palettes
165+
SET color_order = jsonb_build_array(
166+
jsonb_build_object('type', 'color', 'id', color_uuid1),
167+
jsonb_build_object('type', 'color', 'id', color_uuid2),
168+
jsonb_build_object('type', 'color', 'id', color_uuid3)
169+
)
170+
WHERE id = palette_uuid;
171+
172+
RETURN palette_uuid;
173+
END;
174+
$$ LANGUAGE plpgsql;
175+
176+
-- ============================================
177+
-- Views for Common Queries
178+
-- ============================================
179+
180+
-- View: Palettes with colors preloaded
181+
CREATE OR REPLACE VIEW palettes_with_colors AS
182+
SELECT
183+
p.*,
184+
COALESCE(
185+
jsonb_agg(
186+
jsonb_build_object(
187+
'type', 'color',
188+
'id', c.id,
189+
'attributes', jsonb_build_object(
190+
'name', c.name,
191+
'r', c.r,
192+
'g', c.g,
193+
'b', c.b,
194+
'a', c.a,
195+
'created_at', c.created_at
196+
)
197+
)
198+
) FILTER (WHERE c.id IS NOT NULL),
199+
'[]'::jsonb
200+
) as colors
201+
FROM palettes p
202+
LEFT JOIN colors c ON c.palette_id = p.id
203+
WHERE p.user_id = auth.uid()
204+
GROUP BY p.id, p.user_id, p.name, p.is_color_history, p.is_favorite, p.is_locked,
205+
p.selected_color_index, p.color_order, p.created_at, p.updated_at;
206+
207+
-- ============================================
208+
-- Migration Helper Functions
209+
-- ============================================
210+
211+
-- Function to migrate from exported Orbit.js data
212+
CREATE OR REPLACE FUNCTION migrate_orbit_palette_data(
213+
user_uuid UUID,
214+
palette_data JSONB,
215+
color_data JSONB
216+
)
217+
RETURNS UUID AS $$
218+
DECLARE
219+
palette_uuid UUID;
220+
BEGIN
221+
-- Insert palette
222+
INSERT INTO palettes (user_id, name, is_color_history, is_favorite, is_locked, selected_color_index, color_order)
223+
VALUES (
224+
user_uuid,
225+
palette_data->>'name',
226+
(palette_data->>'isColorHistory')::BOOLEAN,
227+
(palette_data->>'isFavorite')::BOOLEAN,
228+
(palette_data->>'isLocked')::BOOLEAN,
229+
COALESCE((palette_data->>'selectedColorIndex')::INTEGER, 0),
230+
COALESCE(palette_data->>'colorOrder', '[]'::jsonb)
231+
)
232+
RETURNING id INTO palette_uuid;
233+
234+
-- Return the new palette UUID for relationships
235+
RETURN palette_uuid;
236+
END;
237+
$$ LANGUAGE plpgsql;
238+
239+
CREATE OR REPLACE FUNCTION migrate_orbit_color_data(
240+
user_uuid UUID,
241+
palette_uuid UUID,
242+
color_data JSONB
243+
)
244+
RETURNS UUID AS $$
245+
BEGIN
246+
-- Insert color
247+
INSERT INTO colors (user_id, palette_id, name, r, g, b, a)
248+
VALUES (
249+
user_uuid,
250+
palette_uuid,
251+
color_data->>'name',
252+
(color_data->>'r')::INTEGER,
253+
(color_data->>'g')::INTEGER,
254+
(color_data->>'b')::INTEGER,
255+
COALESCE((color_data->>'a')::REAL, 1.0)
256+
)
257+
RETURNING id;
258+
END;
259+
$$ LANGUAGE plpgsql;

0 commit comments

Comments
 (0)