import { db } from "../db"; import { conversations, conversationMembers, conversationMessages, users, bots } from "@shared/schema"; import { eq, and, desc, sql } from "drizzle-orm"; import { formatUserName } from "../utils/formatUserName"; export async function isMember(conversationId: number, userId: number): Promise { const [row] = await db .select({ id: conversationMembers.id }) .from(conversationMembers) .where(and( eq(conversationMembers.conversationId, conversationId), eq(conversationMembers.userId, userId), )); return !!row; } export async function getConversationWithMeta(convId: number, requestingUserId: number) { const [conv] = await db .select() .from(conversations) .where(eq(conversations.id, convId)); if (!conv) return null; const members = await db .select({ id: users.id, firstName: users.firstName, middleName: users.middleName, lastName: users.lastName, position: users.position, lastReadAt: conversationMembers.lastReadAt, externalOrgId: conversationMembers.externalOrgId, organizationId: users.organizationId, }) .from(conversationMembers) .innerJoin(users, eq(conversationMembers.userId, users.id)) .where(eq(conversationMembers.conversationId, convId)); const member = await db .select({ lastReadAt: conversationMembers.lastReadAt, reactionUnreadCount: conversationMembers.reactionUnreadCount, mutedAt: conversationMembers.mutedAt }) .from(conversationMembers) .where(and( eq(conversationMembers.conversationId, convId), eq(conversationMembers.userId, requestingUserId), )); const lastReadAt = member[0]?.lastReadAt ?? null; const reactionUnreadCount = member[0]?.reactionUnreadCount ?? 0; const isMuted = member[0]?.mutedAt !== null && member[0]?.mutedAt !== undefined; let unreadCount = 0; if (lastReadAt) { const [row] = await db .select({ count: sql`count(*)::int` }) .from(conversationMessages) .where(and( eq(conversationMessages.conversationId, convId), eq(conversationMessages.isDeleted, false), sql`${conversationMessages.createdAt} > ${lastReadAt}`, sql`(${conversationMessages.authorId} IS NULL OR ${conversationMessages.authorId} != ${requestingUserId})`, )); unreadCount = (row?.count ?? 0) + reactionUnreadCount; } else { const [row] = await db .select({ count: sql`count(*)::int` }) .from(conversationMessages) .where(and( eq(conversationMessages.conversationId, convId), eq(conversationMessages.isDeleted, false), sql`(${conversationMessages.authorId} IS NULL OR ${conversationMessages.authorId} != ${requestingUserId})`, )); unreadCount = (row?.count ?? 0) + reactionUnreadCount; } const [lastMsg] = await db .select({ id: conversationMessages.id, message: conversationMessages.message, createdAt: conversationMessages.createdAt, authorId: conversationMessages.authorId, isDeleted: conversationMessages.isDeleted, }) .from(conversationMessages) .where(eq(conversationMessages.conversationId, convId)) .orderBy(desc(conversationMessages.createdAt)) .limit(1); let displayName = conv.name ?? null; if (conv.type === "direct" && !displayName) { const other = members.find(m => m.id !== requestingUserId); displayName = other ? formatUserName(other) : "Диалог"; } else if (conv.type === "bot_direct" && conv.botId) { const [bot] = await db .select({ name: bots.name }) .from(bots) .where(eq(bots.id, conv.botId)); displayName = bot?.name ?? "Бот"; } const isExternal = members.some(m => m.externalOrgId !== null && m.externalOrgId !== undefined); // Load bot info for bot_direct conversations let botInfo: { id: number; name: string; avatarUrl: string | null } | null = null; if (conv.type === "bot_direct" && conv.botId) { const [bot] = await db .select({ id: bots.id, name: bots.name, avatarUrl: bots.avatarUrl }) .from(bots) .where(eq(bots.id, conv.botId)); botInfo = bot ?? null; } return { ...conv, displayName, members, unreadCount, lastMessage: lastMsg ?? null, isExternal, isMuted, bot: botInfo }; } export async function enrichMessage(msgId: number) { const [msg] = await db .select({ id: conversationMessages.id, conversationId: conversationMessages.conversationId, message: conversationMessages.message, replyToId: conversationMessages.replyToId, mentionedUserIds: conversationMessages.mentionedUserIds, attachments: conversationMessages.attachments, createdAt: conversationMessages.createdAt, updatedAt: conversationMessages.updatedAt, isDeleted: conversationMessages.isDeleted, authorId: conversationMessages.authorId, botId: conversationMessages.botId, authorFirstName: users.firstName, authorMiddleName: users.middleName, authorLastName: users.lastName, }) .from(conversationMessages) .leftJoin(users, eq(conversationMessages.authorId, users.id)) .where(eq(conversationMessages.id, msgId)); if (!msg) return null; let replyTo: { id: number; message: string; author: { id: number; firstName: string; middleName: string; lastName: string } } | null = null; if (msg.replyToId) { const [r] = await db .select({ id: conversationMessages.id, message: conversationMessages.message, authorId: conversationMessages.authorId, authorFirstName: users.firstName, authorMiddleName: users.middleName, authorLastName: users.lastName, }) .from(conversationMessages) .leftJoin(users, eq(conversationMessages.authorId, users.id)) .where(and( eq(conversationMessages.id, msg.replyToId), eq(conversationMessages.conversationId, msg.conversationId), )); if (r && r.authorId) { replyTo = { id: r.id, message: r.message, author: { id: r.authorId, firstName: r.authorFirstName ?? '', middleName: r.authorMiddleName ?? '', lastName: r.authorLastName ?? '' }, }; } } // Load bot info if this is a bot message let botInfo: { id: number; name: string; avatarUrl: string | null } | null = null; if (msg.botId) { const [bot] = await db .select({ id: bots.id, name: bots.name, avatarUrl: bots.avatarUrl }) .from(bots) .where(eq(bots.id, msg.botId)); botInfo = bot ?? null; } return { ...msg, author: msg.authorId ? { id: msg.authorId, firstName: msg.authorFirstName ?? '', middleName: msg.authorMiddleName ?? '', lastName: msg.authorLastName ?? '' } : null, bot: botInfo, replyTo, }; } export async function getMembersForSSE(convId: number, conversationOrgId: number) { return db .select({ userId: conversationMembers.userId, externalOrgId: conversationMembers.externalOrgId }) .from(conversationMembers) .where(eq(conversationMembers.conversationId, convId)) .then(rows => rows.map(r => ({ userId: r.userId, orgId: r.externalOrgId ?? conversationOrgId, }))); }