c3776238c7
1:1 conversations (userAId < userBId dedup pair), messages, per-participant read tracking (conversation_reads). Block relationship is enforced on every send. UI: /messages list + /messages/[id] thread (5s poll), MessageButton on profiles, unread-badged nav icon. This completes the social-feature backlog: notifications, rate limiting, blocking, reporting, search/discovery, mentions, DMs, plus fixes for the recipe-visibility 404, follow race, and 2-level comment thread cap found during the earlier audit. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
46 lines
1.5 KiB
TypeScript
46 lines
1.5 KiB
TypeScript
import { db, conversations, eq, and } from "@epicure/db";
|
|
import { randomUUID } from "crypto";
|
|
|
|
/** Normalizes a pair so (userAId, userBId) is order-independent. */
|
|
function orderPair(idA: string, idB: string): [string, string] {
|
|
return idA < idB ? [idA, idB] : [idB, idA];
|
|
}
|
|
|
|
export async function findOrCreateConversation(userId1: string, userId2: string) {
|
|
const [userAId, userBId] = orderPair(userId1, userId2);
|
|
|
|
const existing = await db.query.conversations.findFirst({
|
|
where: and(eq(conversations.userAId, userAId), eq(conversations.userBId, userBId)),
|
|
});
|
|
if (existing) return existing;
|
|
|
|
const [created] = await db
|
|
.insert(conversations)
|
|
.values({ id: randomUUID(), userAId, userBId })
|
|
.onConflictDoNothing()
|
|
.returning();
|
|
|
|
if (created) return created;
|
|
|
|
// Lost a race with a concurrent insert — fetch what the winner created.
|
|
const row = await db.query.conversations.findFirst({
|
|
where: and(eq(conversations.userAId, userAId), eq(conversations.userBId, userBId)),
|
|
});
|
|
if (!row) throw new Error("Failed to create conversation");
|
|
return row;
|
|
}
|
|
|
|
export function isParticipant(
|
|
conversation: { userAId: string; userBId: string },
|
|
userId: string
|
|
): boolean {
|
|
return conversation.userAId === userId || conversation.userBId === userId;
|
|
}
|
|
|
|
export function otherParticipantId(
|
|
conversation: { userAId: string; userBId: string },
|
|
userId: string
|
|
): string {
|
|
return conversation.userAId === userId ? conversation.userBId : conversation.userAId;
|
|
}
|