Files
Arnaud c3776238c7 feat: direct messages
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>
2026-07-03 22:24:56 +02:00

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;
}