-- ===================================================== -- TABLOS SYSTEM DATABASE SCHEMA -- ===================================================== -- 1. TABLOS TABLE -- Main table for storing tablo/workspace information CREATE TABLE tablos ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), name VARCHAR(255) NOT NULL, description TEXT, color VARCHAR(50) NOT NULL DEFAULT 'bg-blue-500', owner_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), is_archived BOOLEAN DEFAULT FALSE, is_public BOOLEAN DEFAULT FALSE, settings JSONB DEFAULT '{}', -- Indexes for performance CONSTRAINT tablos_name_not_empty CHECK (LENGTH(TRIM(name)) > 0), CONSTRAINT tablos_color_format CHECK (color ~ '^bg-[a-z]+-[0-9]+$') ); -- 2. TABLO MEMBERS TABLE -- Junction table for tablo membership and permissions CREATE TABLE tablo_members ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tablo_id UUID NOT NULL REFERENCES tablos(id) ON DELETE CASCADE, user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, role VARCHAR(50) NOT NULL DEFAULT 'member', permissions JSONB DEFAULT '{"read": true, "write": false, "admin": false}', joined_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), invited_by UUID REFERENCES auth.users(id), -- Ensure unique membership per tablo UNIQUE(tablo_id, user_id), -- Valid roles constraint CONSTRAINT valid_member_role CHECK (role IN ('owner', 'admin', 'member', 'viewer')) ); -- 3. TABLO BOARDS TABLE -- Different boards within a tablo (like Kanban boards, calendars, etc.) CREATE TABLE tablo_boards ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tablo_id UUID NOT NULL REFERENCES tablos(id) ON DELETE CASCADE, name VARCHAR(255) NOT NULL, type VARCHAR(50) NOT NULL DEFAULT 'kanban', description TEXT, position INTEGER NOT NULL DEFAULT 0, settings JSONB DEFAULT '{}', created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), created_by UUID NOT NULL REFERENCES auth.users(id), -- Valid board types CONSTRAINT valid_board_type CHECK (type IN ('kanban', 'calendar', 'table', 'timeline', 'chat')) ); -- 4. TABLO LISTS TABLE -- Lists/columns within boards (for Kanban-style organization) CREATE TABLE tablo_lists ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), board_id UUID NOT NULL REFERENCES tablo_boards(id) ON DELETE CASCADE, name VARCHAR(255) NOT NULL, position INTEGER NOT NULL DEFAULT 0, color VARCHAR(50), settings JSONB DEFAULT '{}', created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); -- 5. TABLO CARDS TABLE -- Individual cards/items within lists CREATE TABLE tablo_cards ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), list_id UUID NOT NULL REFERENCES tablo_lists(id) ON DELETE CASCADE, title VARCHAR(500) NOT NULL, description TEXT, position INTEGER NOT NULL DEFAULT 0, due_date TIMESTAMP WITH TIME ZONE, priority VARCHAR(20) DEFAULT 'medium', labels JSONB DEFAULT '[]', assignees JSONB DEFAULT '[]', attachments JSONB DEFAULT '[]', checklist JSONB DEFAULT '[]', created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), created_by UUID NOT NULL REFERENCES auth.users(id), -- Valid priority levels CONSTRAINT valid_priority CHECK (priority IN ('low', 'medium', 'high', 'urgent')) ); -- 6. TABLO ACTIVITIES TABLE -- Activity log for tracking changes and actions CREATE TABLE tablo_activities ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tablo_id UUID NOT NULL REFERENCES tablos(id) ON DELETE CASCADE, user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, action VARCHAR(100) NOT NULL, entity_type VARCHAR(50) NOT NULL, entity_id UUID, details JSONB DEFAULT '{}', created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), -- Valid entity types CONSTRAINT valid_entity_type CHECK (entity_type IN ('tablo', 'board', 'list', 'card', 'member')) ); -- 7. TABLO CHAT CHANNELS TABLE -- Chat channels within tablos CREATE TABLE tablo_chat_channels ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tablo_id UUID NOT NULL REFERENCES tablos(id) ON DELETE CASCADE, name VARCHAR(255) NOT NULL, type VARCHAR(20) NOT NULL DEFAULT 'public', description TEXT, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), created_by UUID NOT NULL REFERENCES auth.users(id), -- Valid channel types CONSTRAINT valid_channel_type CHECK (type IN ('public', 'private', 'direct')) ); -- 8. TABLO CHAT MESSAGES TABLE -- Messages within chat channels CREATE TABLE tablo_chat_messages ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), channel_id UUID NOT NULL REFERENCES tablo_chat_channels(id) ON DELETE CASCADE, user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, content TEXT NOT NULL, message_type VARCHAR(20) DEFAULT 'text', attachments JSONB DEFAULT '[]', reply_to UUID REFERENCES tablo_chat_messages(id), created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), is_edited BOOLEAN DEFAULT FALSE, -- Valid message types CONSTRAINT valid_message_type CHECK (message_type IN ('text', 'image', 'file', 'system')) ); -- ===================================================== -- INDEXES FOR PERFORMANCE -- ===================================================== -- Tablos indexes CREATE INDEX idx_tablos_owner_id ON tablos(owner_id); CREATE INDEX idx_tablos_created_at ON tablos(created_at DESC); CREATE INDEX idx_tablos_name_search ON tablos USING gin(to_tsvector('french', name)); -- Tablo members indexes CREATE INDEX idx_tablo_members_tablo_id ON tablo_members(tablo_id); CREATE INDEX idx_tablo_members_user_id ON tablo_members(user_id); -- Boards indexes CREATE INDEX idx_tablo_boards_tablo_id ON tablo_boards(tablo_id); CREATE INDEX idx_tablo_boards_position ON tablo_boards(tablo_id, position); -- Lists indexes CREATE INDEX idx_tablo_lists_board_id ON tablo_lists(board_id); CREATE INDEX idx_tablo_lists_position ON tablo_lists(board_id, position); -- Cards indexes CREATE INDEX idx_tablo_cards_list_id ON tablo_cards(list_id); CREATE INDEX idx_tablo_cards_position ON tablo_cards(list_id, position); CREATE INDEX idx_tablo_cards_assignees ON tablo_cards USING gin(assignees); CREATE INDEX idx_tablo_cards_due_date ON tablo_cards(due_date) WHERE due_date IS NOT NULL; -- Activities indexes CREATE INDEX idx_tablo_activities_tablo_id ON tablo_activities(tablo_id); CREATE INDEX idx_tablo_activities_created_at ON tablo_activities(created_at DESC); CREATE INDEX idx_tablo_activities_user_id ON tablo_activities(user_id); -- Chat indexes CREATE INDEX idx_tablo_chat_channels_tablo_id ON tablo_chat_channels(tablo_id); CREATE INDEX idx_tablo_chat_messages_channel_id ON tablo_chat_messages(channel_id); CREATE INDEX idx_tablo_chat_messages_created_at ON tablo_chat_messages(created_at DESC); -- ===================================================== -- ROW LEVEL SECURITY (RLS) POLICIES -- ===================================================== -- Enable RLS on all tables ALTER TABLE tablos ENABLE ROW LEVEL SECURITY; ALTER TABLE tablo_members ENABLE ROW LEVEL SECURITY; ALTER TABLE tablo_boards ENABLE ROW LEVEL SECURITY; ALTER TABLE tablo_lists ENABLE ROW LEVEL SECURITY; ALTER TABLE tablo_cards ENABLE ROW LEVEL SECURITY; ALTER TABLE tablo_activities ENABLE ROW LEVEL SECURITY; ALTER TABLE tablo_chat_channels ENABLE ROW LEVEL SECURITY; ALTER TABLE tablo_chat_messages ENABLE ROW LEVEL SECURITY; -- Tablos policies CREATE POLICY "Users can view tablos they are members of" ON tablos FOR SELECT USING ( id IN ( SELECT tablo_id FROM tablo_members WHERE user_id = auth.uid() ) OR owner_id = auth.uid() OR is_public = true ); CREATE POLICY "Users can create their own tablos" ON tablos FOR INSERT WITH CHECK (owner_id = auth.uid()); CREATE POLICY "Owners and admins can update tablos" ON tablos FOR UPDATE USING ( owner_id = auth.uid() OR id IN ( SELECT tablo_id FROM tablo_members WHERE user_id = auth.uid() AND role IN ('admin', 'owner') ) ); -- Tablo members policies CREATE POLICY "Users can view members of tablos they belong to" ON tablo_members FOR SELECT USING ( tablo_id IN ( SELECT tablo_id FROM tablo_members WHERE user_id = auth.uid() ) ); CREATE POLICY "Owners and admins can manage members" ON tablo_members FOR ALL USING ( tablo_id IN ( SELECT id FROM tablos WHERE owner_id = auth.uid() ) OR tablo_id IN ( SELECT tablo_id FROM tablo_members WHERE user_id = auth.uid() AND role IN ('admin', 'owner') ) ); -- ===================================================== -- TRIGGERS FOR AUTOMATIC UPDATES -- ===================================================== -- Function to 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 triggers to relevant tables CREATE TRIGGER update_tablos_updated_at BEFORE UPDATE ON tablos FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); CREATE TRIGGER update_tablo_boards_updated_at BEFORE UPDATE ON tablo_boards FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); CREATE TRIGGER update_tablo_lists_updated_at BEFORE UPDATE ON tablo_lists FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); CREATE TRIGGER update_tablo_cards_updated_at BEFORE UPDATE ON tablo_cards FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); -- Function to automatically add owner as member CREATE OR REPLACE FUNCTION add_owner_as_member() RETURNS TRIGGER AS $$ BEGIN INSERT INTO tablo_members (tablo_id, user_id, role, permissions) VALUES ( NEW.id, NEW.owner_id, 'owner', '{"read": true, "write": true, "admin": true}'::jsonb ); RETURN NEW; END; $$ language 'plpgsql'; CREATE TRIGGER add_owner_as_member_trigger AFTER INSERT ON tablos FOR EACH ROW EXECUTE FUNCTION add_owner_as_member(); -- ===================================================== -- SAMPLE DATA -- ===================================================== -- Insert sample tablos (assuming user IDs exist) -- Note: Replace with actual user UUIDs from your auth.users table /* INSERT INTO tablos (name, description, color, owner_id) VALUES ('Projet Alpha', 'Développement de la nouvelle application', 'bg-blue-500', 'user-uuid-1'), ('Marketing Q4', 'Campagnes marketing pour le quatrième trimestre', 'bg-green-500', 'user-uuid-2'), ('Équipe Dev', 'Coordination de l''équipe de développement', 'bg-purple-500', 'user-uuid-1'), ('Budget 2024', 'Planification budgétaire pour 2024', 'bg-red-500', 'user-uuid-3'), ('Roadmap', 'Feuille de route produit', 'bg-yellow-500', 'user-uuid-1'), ('Support Client', 'Gestion du support client', 'bg-indigo-500', 'user-uuid-2'); */