1import { internalQuery, internalMutation } from './_generated/server';
2import { v } from 'convex/values';
3
4// Get messages by chat room ID (internal)
5export const getByRoom = internalQuery({
6 args: {
7 roomId: v.id('chatRooms'),
8 limit: v.optional(v.number()),
9 beforeTimestamp: v.optional(v.number())
10 },
11 handler: async (ctx, args) => {
12 const limit = args.limit ?? 100;
13
14 const allMessages = await ctx.db
15 .query('messages')
16 .withIndex('by_chatRoom', (q) => q.eq('chatRoomId', args.roomId))
17 .collect();
18
19 // Filter by timestamp if provided
20 let filtered = allMessages;
21 if (args.beforeTimestamp) {
22 filtered = allMessages.filter((m) => m.timestamp < args.beforeTimestamp!);
23 }
24
25 // Sort by timestamp descending and limit
26 filtered.sort((a, b) => b.timestamp - a.timestamp);
27 const messages = filtered.slice(0, limit);
28
29 return {
30 messages,
31 hasMore: filtered.length > limit
32 };
33 }
34});
35
36// Send message (internal)
37export const send = internalMutation({
38 args: {
39 chatRoomId: v.id('chatRooms'),
40 senderId: v.id('users'),
41 content: v.string(),
42 type: v.optional(v.string()),
43 styleData: v.optional(v.string()),
44 hasFormatting: v.optional(v.boolean()),
45 timestamp: v.optional(v.number())
46 },
47 handler: async (ctx, args) => {
48 // Verify room exists
49 const room = await ctx.db.get(args.chatRoomId);
50 if (!room) {
51 throw new Error('Chat room not found');
52 }
53
54 // Verify user exists
55 const user = await ctx.db.get(args.senderId);
56 if (!user) {
57 throw new Error('User not found');
58 }
59
60 // Direct rooms are restricted to their two members
61 if (room.type === 'direct' && !(room.memberIds ?? []).includes(args.senderId)) {
62 throw new Error('Not a member of this conversation');
63 }
64
65 const messageId = await ctx.db.insert('messages', {
66 chatRoomId: args.chatRoomId,
67 senderId: args.senderId,
68 content: args.content,
69 type: args.type ?? 'chat',
70 timestamp: args.timestamp ?? Date.now(),
71 styleData: args.styleData,
72 hasFormatting: args.hasFormatting ?? false
73 });
74
75 return await ctx.db.get(messageId);
76 }
77});
78
79// Get recent messages across all rooms (internal) - for SSE polling
80export const getRecent = internalQuery({
81 args: {
82 sinceTimestamp: v.number(),
83 limit: v.optional(v.number())
84 },
85 handler: async (ctx, args) => {
86 const limit = args.limit ?? 50;
87
88 const messages = await ctx.db
89 .query('messages')
90 .withIndex('by_timestamp')
91 .filter((q) => q.gt(q.field('timestamp'), args.sinceTimestamp))
92 .take(limit);
93
94 return messages;
95 }
96});