-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdatabase_schema_v2_messages.sql
More file actions
375 lines (332 loc) · 12.8 KB
/
database_schema_v2_messages.sql
File metadata and controls
375 lines (332 loc) · 12.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
-- OpenChat V2 Messages Table Addition
-- TTL-based persistent messaging with pinned message support
-- This extends the existing V2 schema with the messages infrastructure
-- Create messages_v2 table with TTL and pinned message support
CREATE TABLE IF NOT EXISTS messages_v2 (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
user_id UUID NOT NULL REFERENCES users_v2(id) ON DELETE CASCADE,
username VARCHAR(50) NOT NULL,
display_name VARCHAR(100),
content TEXT NOT NULL,
message_type VARCHAR(20) NOT NULL DEFAULT 'direct', -- 'direct' or 'group'
group_id UUID REFERENCES groups(id) ON DELETE CASCADE, -- NULL for direct messages
user_role user_role NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
expires_at TIMESTAMP WITH TIME ZONE NOT NULL, -- TTL for message expiration
is_pinned BOOLEAN DEFAULT FALSE, -- Pinned messages (Operator-only)
-- Constraints
CONSTRAINT valid_message_type CHECK (message_type IN ('direct', 'group')),
CONSTRAINT valid_group_message CHECK (
(message_type = 'direct' AND group_id IS NULL) OR
(message_type = 'group' AND group_id IS NOT NULL)
),
CONSTRAINT valid_pinned_message CHECK (
(is_pinned = FALSE) OR
(is_pinned = TRUE AND user_role = 'operator')
)
);
-- Create indexes for performance
CREATE INDEX IF NOT EXISTS idx_messages_v2_created_at ON messages_v2(created_at DESC);
CREATE INDEX IF NOT EXISTS idx_messages_v2_expires_at ON messages_v2(expires_at);
CREATE INDEX IF NOT EXISTS idx_messages_v2_user_id ON messages_v2(user_id);
CREATE INDEX IF NOT EXISTS idx_messages_v2_message_type ON messages_v2(message_type);
CREATE INDEX IF NOT EXISTS idx_messages_v2_group_id ON messages_v2(group_id);
CREATE INDEX IF NOT EXISTS idx_messages_v2_is_pinned ON messages_v2(is_pinned);
CREATE INDEX IF NOT EXISTS idx_messages_v2_user_role ON messages_v2(user_role);
-- Create app_settings table for system-wide configuration
CREATE TABLE IF NOT EXISTS app_settings (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
setting_key VARCHAR(100) NOT NULL UNIQUE,
setting_value TEXT NOT NULL,
description TEXT,
updated_by UUID REFERENCES users_v2(id) ON DELETE SET NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Insert default settings
INSERT INTO app_settings (setting_key, setting_value, description) VALUES
('default_message_retention_hours', '24', 'Default message retention time in hours for direct chat'),
('guest_session_hours', '24', 'Guest session duration in hours'),
('max_message_length', '1000', 'Maximum message content length'),
('cleanup_interval_minutes', '60', 'How often to run automated cleanup in minutes')
ON CONFLICT (setting_key) DO NOTHING;
-- Enable Row Level Security (RLS)
ALTER TABLE messages_v2 ENABLE ROW LEVEL SECURITY;
ALTER TABLE app_settings ENABLE ROW LEVEL SECURITY;
-- RLS Policies for messages_v2
CREATE POLICY "Anyone can view active messages" ON messages_v2
FOR SELECT USING (expires_at > NOW());
CREATE POLICY "Authenticated users can insert messages" ON messages_v2
FOR INSERT WITH CHECK (
-- Users can only insert messages with their own user_id
user_id = auth.uid() OR
-- Or if they are a guest with valid session
EXISTS (
SELECT 1 FROM users_v2
WHERE users_v2.id = user_id
AND users_v2.role = 'guest'
)
);
CREATE POLICY "Users can delete their own messages" ON messages_v2
FOR DELETE USING (
user_id = auth.uid() OR
-- Operators can delete any message
EXISTS (
SELECT 1 FROM users_v2
WHERE users_v2.id = auth.uid() AND users_v2.role = 'operator'
)
);
CREATE POLICY "Only operators can update messages (for pinning)" ON messages_v2
FOR UPDATE USING (
EXISTS (
SELECT 1 FROM users_v2
WHERE users_v2.id = auth.uid() AND users_v2.role = 'operator'
)
);
-- RLS Policies for app_settings
CREATE POLICY "Anyone can view app settings" ON app_settings
FOR SELECT USING (true);
CREATE POLICY "Only operators can modify app settings" ON app_settings
FOR ALL USING (
EXISTS (
SELECT 1 FROM users_v2
WHERE users_v2.id = auth.uid() AND users_v2.role = 'operator'
)
);
-- Function to calculate TTL based on context
CREATE OR REPLACE FUNCTION calculate_message_ttl(
p_message_type VARCHAR(20),
p_group_id UUID DEFAULT NULL,
p_custom_retention_hours INTEGER DEFAULT NULL
)
RETURNS TIMESTAMP WITH TIME ZONE AS $$
DECLARE
retention_hours INTEGER;
ttl_timestamp TIMESTAMP WITH TIME ZONE;
BEGIN
-- Use custom retention if provided
IF p_custom_retention_hours IS NOT NULL THEN
retention_hours := p_custom_retention_hours;
ELSIF p_message_type = 'group' AND p_group_id IS NOT NULL THEN
-- Get group's message deletion timer
SELECT (message_deletion_timer / 3600) INTO retention_hours
FROM groups
WHERE id = p_group_id AND is_active = true;
-- Fallback to default if group not found
IF retention_hours IS NULL THEN
retention_hours := 24;
END IF;
ELSE
-- Direct messages use default setting
SELECT setting_value::INTEGER INTO retention_hours
FROM app_settings
WHERE setting_key = 'default_message_retention_hours';
-- Fallback to 24 hours if setting not found
IF retention_hours IS NULL THEN
retention_hours := 24;
END IF;
END IF;
-- Calculate TTL timestamp
ttl_timestamp := NOW() + (retention_hours || ' hours')::INTERVAL;
RETURN ttl_timestamp;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- Function to send message with automatic TTL calculation (guest-compatible)
CREATE OR REPLACE FUNCTION send_message_v2_guest(
p_user_id UUID,
p_content TEXT,
p_message_type VARCHAR(20) DEFAULT 'direct',
p_group_id UUID DEFAULT NULL,
p_custom_retention_hours INTEGER DEFAULT NULL
)
RETURNS UUID AS $$
DECLARE
message_id UUID;
user_data RECORD;
ttl_timestamp TIMESTAMP WITH TIME ZONE;
BEGIN
-- Get user data
SELECT username, display_name, role INTO user_data
FROM users_v2
WHERE id = p_user_id;
IF NOT FOUND THEN
RAISE EXCEPTION 'User not found';
END IF;
-- Calculate TTL
ttl_timestamp := calculate_message_ttl(p_message_type, p_group_id, p_custom_retention_hours);
-- Insert message
INSERT INTO messages_v2 (
user_id,
username,
display_name,
content,
message_type,
group_id,
user_role,
expires_at
) VALUES (
p_user_id,
user_data.username,
user_data.display_name,
p_content,
p_message_type,
p_group_id,
user_data.role,
ttl_timestamp
) RETURNING id INTO message_id;
-- Update user's last_active
UPDATE users_v2
SET last_active = NOW(), is_online = true
WHERE id = p_user_id;
RETURN message_id;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- Function to pin/unpin messages (Operator only)
CREATE OR REPLACE FUNCTION toggle_message_pin(
p_message_id UUID,
p_operator_id UUID
)
RETURNS BOOLEAN AS $$
DECLARE
current_pin_status BOOLEAN;
operator_role user_role;
BEGIN
-- Verify operator permissions
SELECT role INTO operator_role
FROM users_v2
WHERE id = p_operator_id;
IF operator_role != 'operator' THEN
RAISE EXCEPTION 'Only operators can pin/unpin messages';
END IF;
-- Get current pin status
SELECT is_pinned INTO current_pin_status
FROM messages_v2
WHERE id = p_message_id;
IF NOT FOUND THEN
RAISE EXCEPTION 'Message not found';
END IF;
-- Toggle pin status
UPDATE messages_v2
SET is_pinned = NOT current_pin_status
WHERE id = p_message_id;
RETURN NOT current_pin_status;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- Function for automated message cleanup
CREATE OR REPLACE FUNCTION cleanup_expired_messages()
RETURNS INTEGER AS $$
DECLARE
deleted_count INTEGER;
BEGIN
-- Delete expired non-pinned messages
DELETE FROM messages_v2
WHERE expires_at < NOW() AND is_pinned = FALSE;
GET DIAGNOSTICS deleted_count = ROW_COUNT;
RETURN deleted_count;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- Function to get message retention hours
CREATE OR REPLACE FUNCTION get_message_retention_hours()
RETURNS INTEGER AS $$
DECLARE
retention_hours INTEGER;
BEGIN
SELECT setting_value::INTEGER INTO retention_hours
FROM app_settings
WHERE setting_key = 'default_message_retention_hours';
RETURN COALESCE(retention_hours, 24);
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- Function to create OAuth user (for Google Auth compatibility)
CREATE OR REPLACE FUNCTION create_oauth_user(
user_id UUID,
user_email VARCHAR(255),
user_username VARCHAR(50),
user_display_name VARCHAR(100),
user_role user_role DEFAULT 'normal'
)
RETURNS UUID AS $$
BEGIN
INSERT INTO users_v2 (
id,
email,
username,
display_name,
role,
theme,
is_online,
last_active,
created_at
) VALUES (
user_id,
user_email,
user_username,
user_display_name,
user_role,
'light',
true,
NOW(),
NOW()
);
RETURN user_id;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- Function to handle real-time subscriptions
CREATE OR REPLACE FUNCTION notify_message_change()
RETURNS TRIGGER AS $$
BEGIN
PERFORM pg_notify('message_changes_v2', json_build_object(
'type', TG_OP,
'record', row_to_json(NEW),
'old_record', row_to_json(OLD)
)::text);
RETURN COALESCE(NEW, OLD);
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- Create triggers for real-time notifications
DROP TRIGGER IF EXISTS messages_v2_notify_trigger ON messages_v2;
CREATE TRIGGER messages_v2_notify_trigger
AFTER INSERT OR UPDATE OR DELETE ON messages_v2
FOR EACH ROW
EXECUTE FUNCTION notify_message_change();
-- Create trigger to automatically update user last_active
CREATE OR REPLACE FUNCTION update_user_last_active_v2()
RETURNS TRIGGER AS $$
BEGIN
UPDATE users_v2
SET last_active = NOW(), is_online = true
WHERE id = NEW.user_id;
RETURN NEW;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
DROP TRIGGER IF EXISTS update_user_last_active_v2_trigger ON messages_v2;
CREATE TRIGGER update_user_last_active_v2_trigger
AFTER INSERT ON messages_v2
FOR EACH ROW
EXECUTE FUNCTION update_user_last_active_v2();
-- Grant permissions
GRANT ALL ON messages_v2 TO anon, authenticated;
GRANT ALL ON app_settings TO anon, authenticated;
GRANT EXECUTE ON FUNCTION send_message_v2_guest(UUID, TEXT, VARCHAR, UUID, INTEGER) TO anon, authenticated;
GRANT EXECUTE ON FUNCTION toggle_message_pin(UUID, UUID) TO anon, authenticated;
GRANT EXECUTE ON FUNCTION cleanup_expired_messages() TO anon, authenticated;
GRANT EXECUTE ON FUNCTION get_message_retention_hours() TO anon, authenticated;
GRANT EXECUTE ON FUNCTION create_oauth_user(UUID, VARCHAR, VARCHAR, VARCHAR, user_role) TO anon, authenticated;
GRANT EXECUTE ON FUNCTION calculate_message_ttl(VARCHAR, UUID, INTEGER) TO anon, authenticated;
-- Comments for documentation
COMMENT ON TABLE messages_v2 IS 'Enhanced messages table with TTL-based persistence and pinned message support';
COMMENT ON TABLE app_settings IS 'System-wide configuration settings';
COMMENT ON FUNCTION send_message_v2_guest(UUID, TEXT, VARCHAR, UUID, INTEGER) IS 'Send message with automatic TTL calculation (guest-compatible)';
COMMENT ON FUNCTION toggle_message_pin(UUID, UUID) IS 'Pin/unpin messages (Operator only)';
COMMENT ON FUNCTION cleanup_expired_messages() IS 'Automated cleanup of expired non-pinned messages';
COMMENT ON FUNCTION create_oauth_user(UUID, VARCHAR, VARCHAR, VARCHAR, user_role) IS 'Create user for OAuth flows with proper permissions';
-- Success message
DO $$
BEGIN
RAISE NOTICE 'OpenChat V2 Messages Infrastructure completed successfully!';
RAISE NOTICE 'New table: messages_v2 with TTL and pinned message support';
RAISE NOTICE 'New table: app_settings for system configuration';
RAISE NOTICE 'Functions: send_message_v2_guest, toggle_message_pin, cleanup_expired_messages';
RAISE NOTICE 'Real-time triggers and RLS policies configured';
RAISE NOTICE 'TTL-based message persistence with automatic cleanup implemented';
END $$;