Files
DiscordClone/convex/readState.ts
Bryan1029384756 e773ab41ae
All checks were successful
Build and Release / build-and-release (push) Successful in 10m9s
feat: Implement Direct Messaging with new frontend components, user search, and backend read state management.
2026-02-11 08:01:51 -06:00

114 lines
2.9 KiB
TypeScript

import { query, mutation } from "./_generated/server";
import { v } from "convex/values";
// Get read state for a single channel
export const getReadState = query({
args: {
userId: v.id("userProfiles"),
channelId: v.id("channels"),
},
returns: v.union(
v.object({
lastReadTimestamp: v.number(),
}),
v.null()
),
handler: async (ctx, args) => {
const state = await ctx.db
.query("channelReadState")
.withIndex("by_user_and_channel", (q) =>
q.eq("userId", args.userId).eq("channelId", args.channelId)
)
.unique();
if (!state) return null;
return { lastReadTimestamp: state.lastReadTimestamp };
},
});
// Mark a channel as read up to a given timestamp
export const markRead = mutation({
args: {
userId: v.id("userProfiles"),
channelId: v.id("channels"),
lastReadTimestamp: v.number(),
},
returns: v.null(),
handler: async (ctx, args) => {
const existing = await ctx.db
.query("channelReadState")
.withIndex("by_user_and_channel", (q) =>
q.eq("userId", args.userId).eq("channelId", args.channelId)
)
.unique();
if (existing) {
// Only update if timestamp is newer
if (args.lastReadTimestamp > existing.lastReadTimestamp) {
await ctx.db.patch(existing._id, {
lastReadTimestamp: args.lastReadTimestamp,
});
}
} else {
await ctx.db.insert("channelReadState", {
userId: args.userId,
channelId: args.channelId,
lastReadTimestamp: args.lastReadTimestamp,
});
}
return null;
},
});
// Get all read states for a user (used by Sidebar)
export const getAllReadStates = query({
args: {
userId: v.id("userProfiles"),
},
returns: v.array(
v.object({
channelId: v.id("channels"),
lastReadTimestamp: v.number(),
})
),
handler: async (ctx, args) => {
const states = await ctx.db
.query("channelReadState")
.withIndex("by_user", (q) => q.eq("userId", args.userId))
.collect();
return states.map((s) => ({
channelId: s.channelId,
lastReadTimestamp: s.lastReadTimestamp,
}));
},
});
// Get the latest message timestamp per channel (used by Sidebar)
export const getLatestMessageTimestamps = query({
args: {
channelIds: v.array(v.id("channels")),
},
returns: v.array(
v.object({
channelId: v.id("channels"),
latestTimestamp: v.number(),
})
),
handler: async (ctx, args) => {
const results = [];
for (const channelId of args.channelIds) {
const latestMsg = await ctx.db
.query("messages")
.withIndex("by_channel", (q) => q.eq("channelId", channelId))
.order("desc")
.first();
if (latestMsg) {
results.push({
channelId,
latestTimestamp: Math.floor(latestMsg._creationTime),
});
}
}
return results;
},
});