1import { mutation } from './_generated/server';
2import { authMutation } from './auth';
3import { v } from 'convex/values';
4
5// Authenticated mutation: Send a message
6export const sendMessage = authMutation({
7 args: {
8 chatRoomId: v.id('chatRooms'),
9 content: v.string(),
10 type: v.optional(v.string()),
11 styleData: v.optional(v.string()),
12 hasFormatting: v.optional(v.boolean())
13 },
14 handler: async (ctx, args) => {
15 // ctx.user is provided by authMutation
16 const user = ctx.user;
17
18 // Verify room exists
19 const room = await ctx.db.get(args.chatRoomId);
20 if (!room) {
21 throw new Error('Chat room not found');
22 }
23 if (room.type === 'direct' && !(room.memberIds ?? []).includes(user._id)) {
24 throw new Error('Not a member of this conversation');
25 }
26
27 const messageId = await ctx.db.insert('messages', {
28 chatRoomId: args.chatRoomId,
29 senderId: user._id,
30 content: args.content,
31 type: args.type ?? 'chat',
32 timestamp: Date.now(),
33 styleData: args.styleData,
34 hasFormatting: args.hasFormatting ?? false
35 });
36
37 const message = await ctx.db.get(messageId);
38
39 // Return enriched message
40 return {
41 id: message!._id,
42 chatRoomId: message!.chatRoomId,
43 senderId: message!.senderId,
44 content: message!.content,
45 type: message!.type,
46 timestamp: message!.timestamp,
47 styleData: message!.styleData,
48 hasFormatting: message!.hasFormatting,
49 sender: {
50 id: user._id,
51 nickname: user.nickname,
52 status: user.status,
53 avatarUrl: user.avatarUrl
54 }
55 };
56 }
57});
58
59// Authenticated mutation: Update current user's status
60export const updateMyStatus = authMutation({
61 args: {
62 status: v.string()
63 },
64 handler: async (ctx, args) => {
65 const validStatuses = ['online', 'away', 'busy', 'offline', 'idle'];
66 if (!validStatuses.includes(args.status)) {
67 throw new Error('Invalid status');
68 }
69
70 await ctx.db.patch(ctx.user._id, {
71 status: args.status,
72 lastSeen: Date.now()
73 });
74
75 const updated = await ctx.db.get(ctx.user._id);
76 return {
77 id: updated!._id,
78 nickname: updated!.nickname,
79 status: updated!.status,
80 avatarUrl: updated!.avatarUrl,
81 lastSeen: updated!.lastSeen
82 };
83 }
84});
85
86// Public mutation: Create or get default room (can be called during init)
87export const getOrCreateDefaultRoom = mutation({
88 args: {},
89 handler: async (ctx) => {
90 // Look for existing General room
91 const rooms = await ctx.db.query('chatRooms').collect();
92 const generalRoom = rooms.find((r) => r.name === 'General' && r.type === 'group');
93
94 if (generalRoom) {
95 return {
96 id: generalRoom._id,
97 name: generalRoom.name,
98 type: generalRoom.type,
99 createdAt: generalRoom.createdAt
100 };
101 }
102
103 // Create default room
104 const roomId = await ctx.db.insert('chatRooms', {
105 name: 'General',
106 type: 'group',
107 createdAt: Date.now()
108 });
109
110 const room = await ctx.db.get(roomId);
111 return {
112 id: room!._id,
113 name: room!.name,
114 type: room!.type,
115 createdAt: room!.createdAt
116 };
117 }
118});