import { query, mutation } from "./_generated/server"; import { v } from "convex/values"; // List recent messages for a channel with reactions + username export const list = query({ args: { channelId: v.id("channels"), userId: v.optional(v.id("userProfiles")), }, returns: v.array(v.any()), handler: async (ctx, args) => { const messages = await ctx.db .query("messages") .withIndex("by_channel", (q) => q.eq("channelId", args.channelId)) .order("desc") .take(50); // Reverse to get chronological order const chronological = messages.reverse(); // Enrich with username, signing key, and reactions const enriched = await Promise.all( chronological.map(async (msg) => { // Get sender info const sender = await ctx.db.get(msg.senderId); // Get reactions for this message const reactionDocs = await ctx.db .query("messageReactions") .withIndex("by_message", (q) => q.eq("messageId", msg._id)) .collect(); // Aggregate reactions const reactions: Record< string, { count: number; me: boolean } > = {}; for (const r of reactionDocs) { if (!reactions[r.emoji]) { reactions[r.emoji] = { count: 0, me: false }; } reactions[r.emoji].count++; if (args.userId && r.userId === args.userId) { reactions[r.emoji].me = true; } } return { id: msg._id, channel_id: msg.channelId, sender_id: msg.senderId, ciphertext: msg.ciphertext, nonce: msg.nonce, signature: msg.signature, key_version: msg.keyVersion, created_at: new Date(msg._creationTime).toISOString(), username: sender?.username || "Unknown", public_signing_key: sender?.publicSigningKey || "", reactions: Object.keys(reactions).length > 0 ? reactions : null, }; }) ); return enriched; }, }); // Send encrypted message export const send = mutation({ args: { channelId: v.id("channels"), senderId: v.id("userProfiles"), ciphertext: v.string(), nonce: v.string(), signature: v.string(), keyVersion: v.number(), }, returns: v.object({ id: v.id("messages") }), handler: async (ctx, args) => { const id = await ctx.db.insert("messages", { channelId: args.channelId, senderId: args.senderId, ciphertext: args.ciphertext, nonce: args.nonce, signature: args.signature, keyVersion: args.keyVersion, }); return { id }; }, }); // Delete a message export const remove = mutation({ args: { id: v.id("messages") }, returns: v.null(), handler: async (ctx, args) => { // Delete reactions first const reactions = await ctx.db .query("messageReactions") .withIndex("by_message", (q) => q.eq("messageId", args.id)) .collect(); for (const r of reactions) { await ctx.db.delete(r._id); } await ctx.db.delete(args.id); return null; }, });