First commit
This commit is contained in:
224
supabase/migrations/001_initial_schema.sql
Normal file
224
supabase/migrations/001_initial_schema.sql
Normal file
@@ -0,0 +1,224 @@
|
||||
-- Root Organization Platform - Initial Schema
|
||||
-- Run this in your Supabase SQL editor
|
||||
|
||||
-- Enable UUID extension
|
||||
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
||||
|
||||
-- Profiles (synced from auth.users)
|
||||
CREATE TABLE profiles (
|
||||
id UUID PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE,
|
||||
email TEXT NOT NULL,
|
||||
full_name TEXT,
|
||||
avatar_url TEXT,
|
||||
created_at TIMESTAMPTZ DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ DEFAULT now()
|
||||
);
|
||||
|
||||
-- Organizations (workspaces)
|
||||
CREATE TABLE organizations (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
name TEXT NOT NULL,
|
||||
slug TEXT UNIQUE NOT NULL,
|
||||
avatar_url TEXT,
|
||||
created_at TIMESTAMPTZ DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ DEFAULT now()
|
||||
);
|
||||
|
||||
-- Organization Members
|
||||
CREATE TABLE org_members (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
org_id UUID REFERENCES organizations(id) ON DELETE CASCADE,
|
||||
user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE,
|
||||
role TEXT NOT NULL CHECK (role IN ('owner', 'admin', 'editor', 'viewer')),
|
||||
invited_at TIMESTAMPTZ DEFAULT now(),
|
||||
joined_at TIMESTAMPTZ,
|
||||
UNIQUE(org_id, user_id)
|
||||
);
|
||||
|
||||
-- Documents/Folders
|
||||
CREATE TABLE documents (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
org_id UUID REFERENCES organizations(id) ON DELETE CASCADE,
|
||||
parent_id UUID REFERENCES documents(id) ON DELETE CASCADE,
|
||||
type TEXT NOT NULL CHECK (type IN ('folder', 'document')),
|
||||
name TEXT NOT NULL,
|
||||
content JSONB,
|
||||
created_by UUID REFERENCES auth.users(id),
|
||||
created_at TIMESTAMPTZ DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ DEFAULT now()
|
||||
);
|
||||
|
||||
-- Kanban Boards
|
||||
CREATE TABLE kanban_boards (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
org_id UUID REFERENCES organizations(id) ON DELETE CASCADE,
|
||||
name TEXT NOT NULL,
|
||||
created_at TIMESTAMPTZ DEFAULT now()
|
||||
);
|
||||
|
||||
-- Kanban Columns
|
||||
CREATE TABLE kanban_columns (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
board_id UUID REFERENCES kanban_boards(id) ON DELETE CASCADE,
|
||||
name TEXT NOT NULL,
|
||||
position INTEGER NOT NULL,
|
||||
color TEXT
|
||||
);
|
||||
|
||||
-- Kanban Cards
|
||||
CREATE TABLE kanban_cards (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
column_id UUID REFERENCES kanban_columns(id) ON DELETE CASCADE,
|
||||
title TEXT NOT NULL,
|
||||
description TEXT,
|
||||
position INTEGER NOT NULL,
|
||||
due_date TIMESTAMPTZ,
|
||||
color TEXT,
|
||||
created_by UUID REFERENCES auth.users(id),
|
||||
created_at TIMESTAMPTZ DEFAULT now()
|
||||
);
|
||||
|
||||
-- Card Assignees
|
||||
CREATE TABLE card_assignees (
|
||||
card_id UUID REFERENCES kanban_cards(id) ON DELETE CASCADE,
|
||||
user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE,
|
||||
PRIMARY KEY (card_id, user_id)
|
||||
);
|
||||
|
||||
-- Calendar Events
|
||||
CREATE TABLE calendar_events (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
org_id UUID REFERENCES organizations(id) ON DELETE CASCADE,
|
||||
title TEXT NOT NULL,
|
||||
description TEXT,
|
||||
start_time TIMESTAMPTZ NOT NULL,
|
||||
end_time TIMESTAMPTZ NOT NULL,
|
||||
all_day BOOLEAN DEFAULT false,
|
||||
color TEXT,
|
||||
recurrence TEXT,
|
||||
created_by UUID REFERENCES auth.users(id),
|
||||
created_at TIMESTAMPTZ DEFAULT now()
|
||||
);
|
||||
|
||||
-- Event Attendees
|
||||
CREATE TABLE event_attendees (
|
||||
event_id UUID REFERENCES calendar_events(id) ON DELETE CASCADE,
|
||||
user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE,
|
||||
status TEXT CHECK (status IN ('pending', 'accepted', 'declined')) DEFAULT 'pending',
|
||||
PRIMARY KEY (event_id, user_id)
|
||||
);
|
||||
|
||||
-- Indexes for performance
|
||||
CREATE INDEX idx_org_members_org ON org_members(org_id);
|
||||
CREATE INDEX idx_org_members_user ON org_members(user_id);
|
||||
CREATE INDEX idx_documents_org ON documents(org_id);
|
||||
CREATE INDEX idx_documents_parent ON documents(parent_id);
|
||||
CREATE INDEX idx_kanban_boards_org ON kanban_boards(org_id);
|
||||
CREATE INDEX idx_kanban_columns_board ON kanban_columns(board_id);
|
||||
CREATE INDEX idx_kanban_cards_column ON kanban_cards(column_id);
|
||||
CREATE INDEX idx_calendar_events_org ON calendar_events(org_id);
|
||||
CREATE INDEX idx_calendar_events_time ON calendar_events(start_time, end_time);
|
||||
|
||||
-- Row Level Security (RLS)
|
||||
ALTER TABLE profiles ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE organizations ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE org_members ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE documents ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE kanban_boards ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE kanban_columns ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE kanban_cards ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE card_assignees ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE calendar_events ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE event_attendees ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
-- Profiles: Users can read all profiles, update their own
|
||||
CREATE POLICY "Profiles are viewable by everyone" ON profiles FOR SELECT USING (true);
|
||||
CREATE POLICY "Users can update own profile" ON profiles FOR UPDATE USING (auth.uid() = id);
|
||||
|
||||
-- Organizations: Members can view, owners/admins can modify
|
||||
CREATE POLICY "Org members can view organizations" ON organizations FOR SELECT
|
||||
USING (EXISTS (SELECT 1 FROM org_members WHERE org_id = organizations.id AND user_id = auth.uid()));
|
||||
|
||||
CREATE POLICY "Org owners/admins can update organizations" ON organizations FOR UPDATE
|
||||
USING (EXISTS (SELECT 1 FROM org_members WHERE org_id = organizations.id AND user_id = auth.uid() AND role IN ('owner', 'admin')));
|
||||
|
||||
CREATE POLICY "Anyone can create organizations" ON organizations FOR INSERT WITH CHECK (true);
|
||||
|
||||
-- Org Members: Members can view, owners/admins can modify
|
||||
CREATE POLICY "Org members can view members" ON org_members FOR SELECT
|
||||
USING (EXISTS (SELECT 1 FROM org_members om WHERE om.org_id = org_members.org_id AND om.user_id = auth.uid()));
|
||||
|
||||
CREATE POLICY "Org owners/admins can manage members" ON org_members FOR ALL
|
||||
USING (EXISTS (SELECT 1 FROM org_members om WHERE om.org_id = org_members.org_id AND om.user_id = auth.uid() AND om.role IN ('owner', 'admin')));
|
||||
|
||||
-- Documents: Based on org membership and role
|
||||
CREATE POLICY "Org members can view documents" ON documents FOR SELECT
|
||||
USING (EXISTS (SELECT 1 FROM org_members WHERE org_id = documents.org_id AND user_id = auth.uid()));
|
||||
|
||||
CREATE POLICY "Editors can manage documents" ON documents FOR ALL
|
||||
USING (EXISTS (SELECT 1 FROM org_members WHERE org_id = documents.org_id AND user_id = auth.uid() AND role IN ('owner', 'admin', 'editor')));
|
||||
|
||||
-- Kanban: Based on org membership
|
||||
CREATE POLICY "Org members can view boards" ON kanban_boards FOR SELECT
|
||||
USING (EXISTS (SELECT 1 FROM org_members WHERE org_id = kanban_boards.org_id AND user_id = auth.uid()));
|
||||
|
||||
CREATE POLICY "Editors can manage boards" ON kanban_boards FOR ALL
|
||||
USING (EXISTS (SELECT 1 FROM org_members WHERE org_id = kanban_boards.org_id AND user_id = auth.uid() AND role IN ('owner', 'admin', 'editor')));
|
||||
|
||||
CREATE POLICY "Org members can view columns" ON kanban_columns FOR SELECT
|
||||
USING (EXISTS (SELECT 1 FROM kanban_boards b JOIN org_members om ON b.org_id = om.org_id WHERE b.id = kanban_columns.board_id AND om.user_id = auth.uid()));
|
||||
|
||||
CREATE POLICY "Editors can manage columns" ON kanban_columns FOR ALL
|
||||
USING (EXISTS (SELECT 1 FROM kanban_boards b JOIN org_members om ON b.org_id = om.org_id WHERE b.id = kanban_columns.board_id AND om.user_id = auth.uid() AND om.role IN ('owner', 'admin', 'editor')));
|
||||
|
||||
CREATE POLICY "Org members can view cards" ON kanban_cards FOR SELECT
|
||||
USING (EXISTS (SELECT 1 FROM kanban_columns c JOIN kanban_boards b ON c.board_id = b.id JOIN org_members om ON b.org_id = om.org_id WHERE c.id = kanban_cards.column_id AND om.user_id = auth.uid()));
|
||||
|
||||
CREATE POLICY "Editors can manage cards" ON kanban_cards FOR ALL
|
||||
USING (EXISTS (SELECT 1 FROM kanban_columns c JOIN kanban_boards b ON c.board_id = b.id JOIN org_members om ON b.org_id = om.org_id WHERE c.id = kanban_cards.column_id AND om.user_id = auth.uid() AND om.role IN ('owner', 'admin', 'editor')));
|
||||
|
||||
-- Calendar: Based on org membership
|
||||
CREATE POLICY "Org members can view events" ON calendar_events FOR SELECT
|
||||
USING (EXISTS (SELECT 1 FROM org_members WHERE org_id = calendar_events.org_id AND user_id = auth.uid()));
|
||||
|
||||
CREATE POLICY "Editors can manage events" ON calendar_events FOR ALL
|
||||
USING (EXISTS (SELECT 1 FROM org_members WHERE org_id = calendar_events.org_id AND user_id = auth.uid() AND role IN ('owner', 'admin', 'editor')));
|
||||
|
||||
-- Trigger to create profile on user signup
|
||||
CREATE OR REPLACE FUNCTION public.handle_new_user()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
INSERT INTO public.profiles (id, email, full_name, avatar_url)
|
||||
VALUES (
|
||||
NEW.id,
|
||||
NEW.email,
|
||||
NEW.raw_user_meta_data->>'full_name',
|
||||
NEW.raw_user_meta_data->>'avatar_url'
|
||||
);
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql SECURITY DEFINER;
|
||||
|
||||
CREATE OR REPLACE TRIGGER on_auth_user_created
|
||||
AFTER INSERT ON auth.users
|
||||
FOR EACH ROW EXECUTE FUNCTION public.handle_new_user();
|
||||
|
||||
-- Function to add creator as owner when org is created
|
||||
CREATE OR REPLACE FUNCTION public.handle_new_org()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
INSERT INTO public.org_members (org_id, user_id, role, joined_at)
|
||||
VALUES (NEW.id, auth.uid(), 'owner', now());
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql SECURITY DEFINER;
|
||||
|
||||
CREATE OR REPLACE TRIGGER on_org_created
|
||||
AFTER INSERT ON organizations
|
||||
FOR EACH ROW EXECUTE FUNCTION public.handle_new_org();
|
||||
|
||||
-- Enable realtime for tables that need live updates
|
||||
ALTER PUBLICATION supabase_realtime ADD TABLE documents;
|
||||
ALTER PUBLICATION supabase_realtime ADD TABLE kanban_columns;
|
||||
ALTER PUBLICATION supabase_realtime ADD TABLE kanban_cards;
|
||||
ALTER PUBLICATION supabase_realtime ADD TABLE calendar_events;
|
||||
44
supabase/migrations/002_card_checklists.sql
Normal file
44
supabase/migrations/002_card_checklists.sql
Normal file
@@ -0,0 +1,44 @@
|
||||
-- Add description to kanban_cards and create checklist tables
|
||||
|
||||
-- Add description column to kanban_cards if not exists
|
||||
ALTER TABLE kanban_cards ADD COLUMN IF NOT EXISTS description TEXT;
|
||||
|
||||
-- Checklist items for kanban cards
|
||||
CREATE TABLE IF NOT EXISTS checklist_items (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
card_id UUID REFERENCES kanban_cards(id) ON DELETE CASCADE,
|
||||
title TEXT NOT NULL,
|
||||
completed BOOLEAN DEFAULT false,
|
||||
position INTEGER NOT NULL,
|
||||
created_at TIMESTAMPTZ DEFAULT now()
|
||||
);
|
||||
|
||||
-- Index for performance
|
||||
CREATE INDEX IF NOT EXISTS idx_checklist_items_card ON checklist_items(card_id);
|
||||
|
||||
-- RLS
|
||||
ALTER TABLE checklist_items ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
-- Checklist items inherit card permissions (via board -> org membership)
|
||||
CREATE POLICY "Org members can view checklist items" ON checklist_items FOR SELECT
|
||||
USING (EXISTS (
|
||||
SELECT 1 FROM kanban_cards c
|
||||
JOIN kanban_columns col ON c.column_id = col.id
|
||||
JOIN kanban_boards b ON col.board_id = b.id
|
||||
JOIN org_members om ON b.org_id = om.org_id
|
||||
WHERE c.id = checklist_items.card_id AND om.user_id = auth.uid()
|
||||
));
|
||||
|
||||
CREATE POLICY "Editors can manage checklist items" ON checklist_items FOR ALL
|
||||
USING (EXISTS (
|
||||
SELECT 1 FROM kanban_cards c
|
||||
JOIN kanban_columns col ON c.column_id = col.id
|
||||
JOIN kanban_boards b ON col.board_id = b.id
|
||||
JOIN org_members om ON b.org_id = om.org_id
|
||||
WHERE c.id = checklist_items.card_id
|
||||
AND om.user_id = auth.uid()
|
||||
AND om.role IN ('owner', 'admin', 'editor')
|
||||
));
|
||||
|
||||
-- Enable realtime for checklist items
|
||||
ALTER PUBLICATION supabase_realtime ADD TABLE checklist_items;
|
||||
31
supabase/migrations/003_google_calendar.sql
Normal file
31
supabase/migrations/003_google_calendar.sql
Normal file
@@ -0,0 +1,31 @@
|
||||
-- Google Calendar integration
|
||||
|
||||
-- Store Google Calendar connection per user
|
||||
CREATE TABLE IF NOT EXISTS google_calendar_connections (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE UNIQUE,
|
||||
access_token TEXT NOT NULL,
|
||||
refresh_token TEXT NOT NULL,
|
||||
token_expires_at TIMESTAMPTZ NOT NULL,
|
||||
calendar_id TEXT, -- specific calendar to sync with
|
||||
created_at TIMESTAMPTZ DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ DEFAULT now()
|
||||
);
|
||||
|
||||
-- Link local events to Google Calendar events
|
||||
ALTER TABLE calendar_events ADD COLUMN IF NOT EXISTS google_event_id TEXT;
|
||||
ALTER TABLE calendar_events ADD COLUMN IF NOT EXISTS synced_at TIMESTAMPTZ;
|
||||
|
||||
-- Index
|
||||
CREATE INDEX IF NOT EXISTS idx_google_calendar_user ON google_calendar_connections(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_calendar_events_google ON calendar_events(google_event_id) WHERE google_event_id IS NOT NULL;
|
||||
|
||||
-- RLS
|
||||
ALTER TABLE google_calendar_connections ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
-- Users can only see/manage their own connections
|
||||
CREATE POLICY "Users can view own Google connection" ON google_calendar_connections
|
||||
FOR SELECT USING (auth.uid() = user_id);
|
||||
|
||||
CREATE POLICY "Users can manage own Google connection" ON google_calendar_connections
|
||||
FOR ALL USING (auth.uid() = user_id);
|
||||
Reference in New Issue
Block a user