Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
298 changes: 298 additions & 0 deletions COMPLETE-ALL-TABLES.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,298 @@
-- ============================================================
-- DEVCONNECT COMPLETE DATABASE SCHEMA
-- ============================================================
-- This includes ALL tables: posts, communities, events, AND messaging
-- All table names are lowercase to match the app code
-- Run this AFTER running SAFE-database-setup.sql
-- ============================================================

-- MESSAGING TABLES
-- ============================================================

-- 1. CONVERSATIONS TABLE
CREATE TABLE IF NOT EXISTS conversations (
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
name TEXT,
type TEXT NOT NULL CHECK (type IN ('direct', 'group')),
description TEXT,
is_private BOOLEAN DEFAULT true,
created_by UUID REFERENCES auth.users(id),
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

CREATE INDEX IF NOT EXISTS idx_conversations_type ON conversations(type);
ALTER TABLE conversations ENABLE ROW LEVEL SECURITY;

DROP POLICY IF EXISTS "Users can view conversations they participate in" ON conversations;
DROP POLICY IF EXISTS "Users can create conversations" ON conversations;
DROP POLICY IF EXISTS "Admins can update conversations" ON conversations;

CREATE POLICY "Users can view conversations they participate in" ON conversations
FOR SELECT USING (
id IN (
SELECT conversation_id FROM conversation_participants
WHERE user_id = auth.uid()
)
);

CREATE POLICY "Users can create conversations" ON conversations
FOR INSERT WITH CHECK (created_by = auth.uid());

CREATE POLICY "Admins can update conversations" ON conversations
FOR UPDATE USING (
id IN (
SELECT conversation_id FROM conversation_participants
WHERE user_id = auth.uid() AND role = 'admin'
)
);

-- 2. CONVERSATION PARTICIPANTS TABLE
CREATE TABLE IF NOT EXISTS conversation_participants (
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
conversation_id BIGINT NOT NULL REFERENCES conversations(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES auth.users(id),
role TEXT DEFAULT 'member' CHECK (role IN ('admin', 'member')),
joined_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
last_read_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(conversation_id, user_id)
);

CREATE INDEX IF NOT EXISTS idx_conversation_participants_user_id ON conversation_participants(user_id);
CREATE INDEX IF NOT EXISTS idx_conversation_participants_conversation_id ON conversation_participants(conversation_id);

ALTER TABLE conversation_participants ENABLE ROW LEVEL SECURITY;

DROP POLICY IF EXISTS "Users can view participants of their conversations" ON conversation_participants;
DROP POLICY IF EXISTS "Admins can manage participants" ON conversation_participants;

CREATE POLICY "Users can view participants of their conversations" ON conversation_participants
FOR SELECT USING (
conversation_id IN (
SELECT conversation_id FROM conversation_participants
WHERE user_id = auth.uid()
)
);

CREATE POLICY "Admins can manage participants" ON conversation_participants
FOR ALL USING (
conversation_id IN (
SELECT conversation_id FROM conversation_participants
WHERE user_id = auth.uid() AND role = 'admin'
)
);

-- 3. MESSAGES TABLE
CREATE TABLE IF NOT EXISTS messages (
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
conversation_id BIGINT NOT NULL REFERENCES conversations(id) ON DELETE CASCADE,
sender_id UUID NOT NULL REFERENCES auth.users(id),
content TEXT NOT NULL,
message_type TEXT DEFAULT 'text' CHECK (message_type IN ('text', 'file', 'image')),
file_url TEXT,
file_name TEXT,
reply_to_id BIGINT REFERENCES messages(id),
is_edited BOOLEAN DEFAULT false,
is_deleted BOOLEAN DEFAULT false,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

CREATE INDEX IF NOT EXISTS idx_messages_conversation_id ON messages(conversation_id);
CREATE INDEX IF NOT EXISTS idx_messages_sender_id ON messages(sender_id);
CREATE INDEX IF NOT EXISTS idx_messages_created_at ON messages(created_at DESC);

ALTER TABLE messages ENABLE ROW LEVEL SECURITY;

DROP POLICY IF EXISTS "Users can view messages in their conversations" ON messages;
DROP POLICY IF EXISTS "Users can send messages to their conversations" ON messages;
DROP POLICY IF EXISTS "Users can edit their own messages" ON messages;

CREATE POLICY "Users can view messages in their conversations" ON messages
FOR SELECT USING (
conversation_id IN (
SELECT conversation_id FROM conversation_participants
WHERE user_id = auth.uid()
)
);

CREATE POLICY "Users can send messages to their conversations" ON messages
FOR INSERT WITH CHECK (
sender_id = auth.uid() AND
conversation_id IN (
SELECT conversation_id FROM conversation_participants
WHERE user_id = auth.uid()
)
);

CREATE POLICY "Users can edit their own messages" ON messages
FOR UPDATE USING (sender_id = auth.uid());

-- 4. MESSAGE REACTIONS TABLE
CREATE TABLE IF NOT EXISTS message_reactions (
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
message_id BIGINT NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES auth.users(id),
emoji TEXT NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(message_id, user_id, emoji)
);

CREATE INDEX IF NOT EXISTS idx_message_reactions_message_id ON message_reactions(message_id);

ALTER TABLE message_reactions ENABLE ROW LEVEL SECURITY;

DROP POLICY IF EXISTS "Users can view reactions in their conversations" ON message_reactions;
DROP POLICY IF EXISTS "Users can react to messages" ON message_reactions;
DROP POLICY IF EXISTS "Users can remove their own reactions" ON message_reactions;

CREATE POLICY "Users can view reactions in their conversations" ON message_reactions
FOR SELECT USING (
message_id IN (
SELECT id FROM messages WHERE conversation_id IN (
SELECT conversation_id FROM conversation_participants
WHERE user_id = auth.uid()
)
)
);

CREATE POLICY "Users can react to messages" ON message_reactions
FOR INSERT WITH CHECK (
user_id = auth.uid() AND
message_id IN (
SELECT id FROM messages WHERE conversation_id IN (
SELECT conversation_id FROM conversation_participants
WHERE user_id = auth.uid()
)
)
);

CREATE POLICY "Users can remove their own reactions" ON message_reactions
FOR DELETE USING (user_id = auth.uid());

-- 5. USER PRESENCE TABLE
CREATE TABLE IF NOT EXISTS user_presence (
user_id UUID PRIMARY KEY REFERENCES auth.users(id),
status TEXT DEFAULT 'offline' CHECK (status IN ('online', 'away', 'busy', 'offline')),
last_seen TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

CREATE INDEX IF NOT EXISTS idx_user_presence_status ON user_presence(status);

ALTER TABLE user_presence ENABLE ROW LEVEL SECURITY;

DROP POLICY IF EXISTS "Users can view all user presence" ON user_presence;
DROP POLICY IF EXISTS "Users can update their own presence" ON user_presence;

CREATE POLICY "Users can view all user presence" ON user_presence
FOR SELECT USING (true);

CREATE POLICY "Users can update their own presence" ON user_presence
FOR ALL USING (user_id = auth.uid());

-- 6. TYPING INDICATORS TABLE
CREATE TABLE IF NOT EXISTS typing_indicators (
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
conversation_id BIGINT NOT NULL REFERENCES conversations(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES auth.users(id),
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(conversation_id, user_id)
);

CREATE INDEX IF NOT EXISTS idx_typing_indicators_conversation_id ON typing_indicators(conversation_id);

ALTER TABLE typing_indicators ENABLE ROW LEVEL SECURITY;

DROP POLICY IF EXISTS "Users can view typing indicators in their conversations" ON typing_indicators;
DROP POLICY IF EXISTS "Users can manage their own typing indicators" ON typing_indicators;

CREATE POLICY "Users can view typing indicators in their conversations" ON typing_indicators
FOR SELECT USING (
conversation_id IN (
SELECT conversation_id FROM conversation_participants
WHERE user_id = auth.uid()
)
);

CREATE POLICY "Users can manage their own typing indicators" ON typing_indicators
FOR ALL USING (user_id = auth.uid());

-- 7. PINNED MESSAGES TABLE
CREATE TABLE IF NOT EXISTS pinned_messages (
id BIGINT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
conversation_id BIGINT NOT NULL REFERENCES conversations(id) ON DELETE CASCADE,
message_id BIGINT NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
pinned_by UUID NOT NULL REFERENCES auth.users(id),
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(conversation_id, message_id)
);

ALTER TABLE pinned_messages ENABLE ROW LEVEL SECURITY;

DROP POLICY IF EXISTS "Users can view pinned messages in their conversations" ON pinned_messages;
DROP POLICY IF EXISTS "Admins can manage pinned messages" ON pinned_messages;

CREATE POLICY "Users can view pinned messages in their conversations" ON pinned_messages
FOR SELECT USING (
conversation_id IN (
SELECT conversation_id FROM conversation_participants
WHERE user_id = auth.uid()
)
);

CREATE POLICY "Admins can manage pinned messages" ON pinned_messages
FOR ALL USING (
conversation_id IN (
SELECT conversation_id FROM conversation_participants
WHERE user_id = auth.uid() AND role = 'admin'
)
);

-- TRIGGERS AND FUNCTIONS
-- ============================================================

-- Function to update conversation timestamp
CREATE OR REPLACE FUNCTION update_conversation_timestamp()
RETURNS TRIGGER AS $$
BEGIN
UPDATE conversations
SET updated_at = NOW()
WHERE id = NEW.conversation_id;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;

DROP TRIGGER IF EXISTS update_conversation_on_message ON messages;
CREATE TRIGGER update_conversation_on_message
AFTER INSERT ON messages
FOR EACH ROW
EXECUTE FUNCTION update_conversation_timestamp();

-- Function to clean up old typing indicators
CREATE OR REPLACE FUNCTION cleanup_typing_indicators()
RETURNS void AS $$
BEGIN
DELETE FROM typing_indicators
WHERE created_at < NOW() - INTERVAL '10 seconds';
END;
$$ LANGUAGE plpgsql;

-- ============================================================
-- VERIFICATION
-- ============================================================
SELECT
'βœ… Messaging tables setup complete!' as message,
COUNT(*) as table_count
FROM information_schema.tables
WHERE table_schema = 'public'
AND table_name IN ('conversations', 'conversation_participants', 'messages', 'message_reactions', 'user_presence', 'typing_indicators', 'pinned_messages');

-- List all messaging tables
SELECT
table_name,
(SELECT COUNT(*) FROM information_schema.columns WHERE table_name = t.table_name AND table_schema = 'public') as column_count
FROM information_schema.tables t
WHERE table_schema = 'public'
AND table_name IN ('conversations', 'conversation_participants', 'messages', 'message_reactions', 'user_presence', 'typing_indicators', 'pinned_messages')
ORDER BY table_name;
Loading