Update environment configurations for client and server, refactor ChatPopup to accept sessionId as a prop, enhance chat message handling with senderName and guestId, and improve error logging in chat message processing. Adjust useChatHistory and useWebRTC hooks for better state management and message retrieval.
This commit is contained in:
+4
-4
@@ -1,4 +1,4 @@
|
||||
# VITE_API_URL=http://localhost:3000
|
||||
# VITE_WEBRTC_URL=http://localhost:3001
|
||||
VITE_API_URL=https://stream.graff.estate/api
|
||||
VITE_WEBRTC_URL=https://stream.graff.estate
|
||||
VITE_API_URL=http://localhost:3000
|
||||
VITE_WEBRTC_URL=http://localhost:3001
|
||||
# VITE_API_URL=https://stream.graff.estate/api
|
||||
# VITE_WEBRTC_URL=https://stream.graff.estate
|
||||
@@ -1,25 +1,48 @@
|
||||
import { useRef, useEffect, useState } from "react";
|
||||
import SendIcon from "../icons/SendIcon";
|
||||
import Button from "../ui/Button";
|
||||
import { useMe } from "../../hooks/useAuth";
|
||||
import clsx from "clsx";
|
||||
import PopupWrapper from "../PopupWrapper";
|
||||
import DraggableContainer from "../DraggableContainer";
|
||||
import { useWebRTC } from "../../hooks/useWebRTC";
|
||||
import { useChatHistory } from "../../hooks/useChatHistory";
|
||||
import { useParams } from "react-router";
|
||||
import { useMe } from "../../hooks/useAuth";
|
||||
|
||||
export default function ChatPopup() {
|
||||
interface ChatPopupProps {
|
||||
sessionId?: string;
|
||||
}
|
||||
|
||||
export default function ChatPopup({ sessionId: sessionIdProp }: ChatPopupProps = {}) {
|
||||
const headerRef = useRef<HTMLDivElement>(null);
|
||||
const { id: sessionId } = useParams<{ id: string }>();
|
||||
// Получаем sessionId из пропсов или из URL параметров
|
||||
const { id: sessionIdFromParams } = useParams<{ id: string }>();
|
||||
const sessionId = sessionIdProp || sessionIdFromParams;
|
||||
const {
|
||||
chatMessages: realtimeMessages,
|
||||
sendMessage,
|
||||
currentUserId,
|
||||
} = useWebRTC();
|
||||
|
||||
// Получаем данные текущего пользователя (если авторизован)
|
||||
const { data: user } = useMe();
|
||||
|
||||
// currentUserId содержит либо userId (если авторизован), либо guestId (если гость)
|
||||
// Это и есть наш идентификатор для сравнения с senderId в сообщениях
|
||||
const myIdentifier = currentUserId;
|
||||
|
||||
console.log("[ChatPopup] Rendering with sessionId:", sessionId);
|
||||
console.log("[ChatPopup] My identifier:", myIdentifier, "isAuthenticated:", !!user);
|
||||
|
||||
// Загружаем историю через REST API
|
||||
const { data: historyMessages = [], isLoading } = useChatHistory(sessionId);
|
||||
const { data: historyMessages = [], isLoading, error } = useChatHistory(sessionId);
|
||||
|
||||
console.log("[ChatPopup] Chat history state:", {
|
||||
historyMessages: historyMessages.length,
|
||||
isLoading,
|
||||
error,
|
||||
realtimeMessages: realtimeMessages.length,
|
||||
});
|
||||
|
||||
// Объединяем историю и realtime сообщения
|
||||
const historyIds = new Set(historyMessages.map((m) => m.id));
|
||||
@@ -28,8 +51,11 @@ export default function ChatPopup() {
|
||||
);
|
||||
const allMessages = [...historyMessages, ...newRealtimeMessages];
|
||||
|
||||
console.log("[ChatPopup] All messages count:", allMessages.length);
|
||||
|
||||
function onMessageSend(message: string) {
|
||||
sendMessage(message);
|
||||
// Передаем имя пользователя и флаг авторизации
|
||||
sendMessage(message, user?.fullName, !!user);
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -48,7 +74,7 @@ export default function ChatPopup() {
|
||||
<div className="flex flex-col 2xl:h-[19.444vw] max-sm:h-[87.5dvh] 2xl:-m-[1.389vw] -m-5">
|
||||
<MessageFeed
|
||||
messages={allMessages}
|
||||
currentUserId={currentUserId}
|
||||
currentUserId={myIdentifier}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
<MessageInput onMessageSend={onMessageSend} />
|
||||
@@ -62,6 +88,7 @@ interface MessageFeedProps {
|
||||
messages: Array<{
|
||||
id: string;
|
||||
senderId: string;
|
||||
senderName?: string;
|
||||
content: string;
|
||||
timestamp: Date | string;
|
||||
}>;
|
||||
@@ -73,6 +100,14 @@ function MessageFeed({ messages, currentUserId, isLoading }: MessageFeedProps) {
|
||||
const messagesEndRef = useRef<HTMLDivElement>(null);
|
||||
const prevMessageCountRef = useRef(0);
|
||||
|
||||
console.log("[MessageFeed] Rendering with currentUserId:", currentUserId);
|
||||
console.log("[MessageFeed] Messages:", messages.map(m => ({
|
||||
id: m.id,
|
||||
senderId: m.senderId,
|
||||
isFromMe: m.senderId === currentUserId,
|
||||
content: m.content.substring(0, 20)
|
||||
})));
|
||||
|
||||
// Умный скролл - только при добавлении новых сообщений
|
||||
useEffect(() => {
|
||||
const currentCount = messages.length;
|
||||
@@ -124,8 +159,18 @@ function MessageFeed({ messages, currentUserId, isLoading }: MessageFeedProps) {
|
||||
}
|
||||
)}
|
||||
isFromMe={message.senderId === currentUserId}
|
||||
senderName={message.senderName}
|
||||
/>
|
||||
))}
|
||||
|
||||
{/* <MessageItem content="test" timestamp="12:00" isFromMe={false} />
|
||||
<MessageItem content="test123" timestamp="12:00" isFromMe={true} />
|
||||
<MessageItem content="testqwe" timestamp="12:00" isFromMe={true} />
|
||||
<MessageItem
|
||||
content={`asdasdasd\nasdasda\nasdasd`}
|
||||
timestamp="12:00"
|
||||
isFromMe={false}
|
||||
/> */}
|
||||
<div ref={messagesEndRef} />
|
||||
</div>
|
||||
)}
|
||||
@@ -137,11 +182,15 @@ interface MessageItemProps {
|
||||
timestamp: string;
|
||||
content: string;
|
||||
isFromMe: boolean;
|
||||
senderName?: string;
|
||||
}
|
||||
|
||||
function MessageItem({ timestamp, content, isFromMe }: MessageItemProps) {
|
||||
const { data: user } = useMe();
|
||||
|
||||
function MessageItem({
|
||||
timestamp,
|
||||
content,
|
||||
isFromMe,
|
||||
senderName,
|
||||
}: MessageItemProps) {
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
@@ -159,9 +208,11 @@ function MessageItem({ timestamp, content, isFromMe }: MessageItemProps) {
|
||||
>
|
||||
<div className="break-words text-s 2xl:space-y-[0.278vw] space-y-1">
|
||||
{!isFromMe && (
|
||||
<div className="caption-s text-[#7B60F3]">{user?.fullName}</div>
|
||||
<div className="caption-s text-[#7B60F3]">
|
||||
{senderName || "Guest"}
|
||||
</div>
|
||||
)}
|
||||
<div>{content}</div>
|
||||
<div className="whitespace-pre-wrap">{content}</div>
|
||||
</div>
|
||||
<span className="opacity-30 caption-xs">{timestamp}</span>
|
||||
</div>
|
||||
|
||||
@@ -33,7 +33,7 @@ function ControlsPopover({ session }: ControlsPopoverProps) {
|
||||
|
||||
function handleClickOpenChatPopup() {
|
||||
setIsOpened(false);
|
||||
setPopup(<ChatPopup />);
|
||||
setPopup(<ChatPopup sessionId={session?.id} />);
|
||||
}
|
||||
|
||||
function handleClickOpenParticipantsPopup() {
|
||||
|
||||
@@ -10,22 +10,37 @@ interface ChatHistoryResponse {
|
||||
}
|
||||
|
||||
export const useChatHistory = (sessionId: string | undefined, enabled = true) => {
|
||||
console.log("[useChatHistory] Hook called with:", { sessionId, enabled, willExecute: enabled && !!sessionId });
|
||||
|
||||
return useQuery({
|
||||
queryKey: ["chat-history", sessionId],
|
||||
queryFn: async () => {
|
||||
console.log("[useChatHistory] Fetching chat history for session:", sessionId);
|
||||
|
||||
if (!sessionId) {
|
||||
console.error("[useChatHistory] Session ID is required but not provided");
|
||||
throw new Error("Session ID is required");
|
||||
}
|
||||
|
||||
const response = await api
|
||||
.get(`sessions/${sessionId}/messages`)
|
||||
.json<ChatHistoryResponse>();
|
||||
try {
|
||||
console.log("[useChatHistory] Making API request to:", `sessions/${sessionId}/messages`);
|
||||
const response = await api
|
||||
.get(`sessions/${sessionId}/messages`)
|
||||
.json<ChatHistoryResponse>();
|
||||
|
||||
if (!response.success) {
|
||||
throw new Error(response.error || "Failed to load chat history");
|
||||
console.log("[useChatHistory] API response:", response);
|
||||
|
||||
if (!response.success) {
|
||||
console.error("[useChatHistory] API returned error:", response.error);
|
||||
throw new Error(response.error || "Failed to load chat history");
|
||||
}
|
||||
|
||||
console.log("[useChatHistory] Successfully loaded", response.messages.length, "messages");
|
||||
return response.messages;
|
||||
} catch (error) {
|
||||
console.error("[useChatHistory] Error fetching chat history:", error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
return response.messages;
|
||||
},
|
||||
enabled: enabled && !!sessionId,
|
||||
staleTime: 1000 * 60 * 5, // 5 минут - история считается актуальной
|
||||
|
||||
@@ -202,9 +202,9 @@ export const useWebRTC = (roomId?: string, autoJoin = false) => {
|
||||
setIsVideoMuted(!newState);
|
||||
};
|
||||
|
||||
const sendMessage = (content: string) => {
|
||||
const sendMessage = (content: string, senderName?: string, isAuthenticated?: boolean) => {
|
||||
if (!webrtcServiceInstance) return;
|
||||
webrtcServiceInstance.sendChatMessage(content);
|
||||
webrtcServiceInstance.sendChatMessage(content, senderName, isAuthenticated);
|
||||
};
|
||||
|
||||
const joinRoom = async (roomId: string) => {
|
||||
|
||||
@@ -300,7 +300,9 @@ function setupSocketListeners() {
|
||||
});
|
||||
|
||||
socket.on("chat-error", (error: { message: string }) => {
|
||||
console.error("📨 Chat error:", error.message);
|
||||
console.error("📨 Chat error received from server:", error);
|
||||
console.error("📨 Error message:", error.message);
|
||||
alert(`Ошибка при отправке сообщения: ${error.message}`);
|
||||
callAllCallbacks("onError", new Error(error.message));
|
||||
});
|
||||
|
||||
@@ -850,17 +852,22 @@ function updateSpeakingState(isSpeaking: boolean): void {
|
||||
});
|
||||
}
|
||||
|
||||
function sendChatMessage(content: string, userName?: string): void {
|
||||
function sendChatMessage(content: string, senderName?: string, isAuthenticated?: boolean): void {
|
||||
if (!state || !content.trim() || !state.roomId) return;
|
||||
|
||||
console.log("📤 Sending message via Socket.IO:", content);
|
||||
|
||||
// Определяем userId и guestId
|
||||
const userId = isAuthenticated ? state.userId : null;
|
||||
const guestId = !isAuthenticated ? state.userId : null;
|
||||
|
||||
// Отправляем сообщение через Socket.IO
|
||||
state.socket.emit("chat-message", {
|
||||
roomId: state.roomId,
|
||||
userId: state.userId,
|
||||
userId,
|
||||
guestId,
|
||||
content: content.trim(),
|
||||
userName: userName || "Anonymous",
|
||||
senderName: senderName || null,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@ function SessionPage() {
|
||||
|
||||
function handleChatOpen() {
|
||||
console.log("handleChatOpen");
|
||||
setPopup(<ChatPopup />);
|
||||
setPopup(<ChatPopup sessionId={id} />);
|
||||
}
|
||||
|
||||
function handleParticipantsOpen() {
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
DATABASE_URL=postgres://postgres:v1sq3vD5faXL@194.26.138.94:5432/stream
|
||||
JWT_SECRET=b5cf2bd3894fb24191f13dc9dddaeecccc92d0ee298e7ee41c2d0aab51c28fa1
|
||||
PORT=6000
|
||||
SOCKET_PORT=6001
|
||||
PORT=3000
|
||||
SOCKET_PORT=3001
|
||||
@@ -1,4 +1,4 @@
|
||||
import { pgTable, uuid, text, timestamp, pgEnum } from "drizzle-orm/pg-core";
|
||||
import { pgTable, uuid, text, timestamp, pgEnum, varchar } from "drizzle-orm/pg-core";
|
||||
import { relations } from "drizzle-orm";
|
||||
import { createInsertSchema, createSelectSchema } from "drizzle-zod";
|
||||
import { serverSessions } from "./serverSessions";
|
||||
@@ -13,6 +13,8 @@ export const chatMessages = pgTable("chat_messages", {
|
||||
.notNull()
|
||||
.references(() => serverSessions.id, { onDelete: "cascade" }),
|
||||
userId: uuid("user_id").references(() => users.id), // Nullable для системных сообщений или анонимных пользователей
|
||||
guestId: uuid("guest_id"), // ID гостя (для неавторизованных пользователей)
|
||||
senderName: varchar("sender_name", { length: 255 }).notNull(), // Имя отправителя (из БД для авторизованных, "Guest" для неавторизованных)
|
||||
content: text("content").notNull(),
|
||||
type: messageTypeEnum("type").notNull().default("text"),
|
||||
createdAt: timestamp("created_at", { withTimezone: true })
|
||||
|
||||
+179
-94
@@ -83,72 +83,79 @@ io.on("connection", (socket) => {
|
||||
console.log(`[WebRTC] User connected: ${socket.id}`);
|
||||
|
||||
// Присоединение к комнате
|
||||
socket.on("join-room", async ({ roomId, userId, isAudioEnabled, isVideoEnabled }) => {
|
||||
console.log(
|
||||
`[WebRTC] User ${userId} (socket: ${socket.id}) joining room ${roomId}, audio: ${isAudioEnabled}, video: ${isVideoEnabled}`
|
||||
);
|
||||
|
||||
// Покинуть предыдущую комнату если была
|
||||
const existingUser = users.get(userId);
|
||||
if (existingUser?.roomId) {
|
||||
socket.on(
|
||||
"join-room",
|
||||
async ({ roomId, userId, isAudioEnabled, isVideoEnabled }) => {
|
||||
console.log(
|
||||
`[WebRTC] User ${userId} leaving previous room ${existingUser.roomId}`
|
||||
`[WebRTC] User ${userId} (socket: ${socket.id}) joining room ${roomId}, audio: ${isAudioEnabled}, video: ${isVideoEnabled}`
|
||||
);
|
||||
socket.leave(existingUser.roomId);
|
||||
const prevRoom = rooms.get(existingUser.roomId);
|
||||
if (prevRoom) {
|
||||
prevRoom.participants.delete(userId);
|
||||
socket.to(existingUser.roomId).emit("user-left", userId);
|
||||
|
||||
// Покинуть предыдущую комнату если была
|
||||
const existingUser = users.get(userId);
|
||||
if (existingUser?.roomId) {
|
||||
console.log(
|
||||
`[WebRTC] User ${userId} leaving previous room ${existingUser.roomId}`
|
||||
);
|
||||
socket.leave(existingUser.roomId);
|
||||
const prevRoom = rooms.get(existingUser.roomId);
|
||||
if (prevRoom) {
|
||||
prevRoom.participants.delete(userId);
|
||||
socket.to(existingUser.roomId).emit("user-left", userId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Присоединиться к новой комнате
|
||||
socket.join(roomId);
|
||||
// Присоединиться к новой комнате
|
||||
socket.join(roomId);
|
||||
|
||||
// Создать комнату если не существует
|
||||
if (!rooms.has(roomId)) {
|
||||
console.log(`[WebRTC] Creating new room: ${roomId}`);
|
||||
rooms.set(roomId, {
|
||||
id: roomId,
|
||||
participants: new Set(),
|
||||
// Создать комнату если не существует
|
||||
if (!rooms.has(roomId)) {
|
||||
console.log(`[WebRTC] Creating new room: ${roomId}`);
|
||||
rooms.set(roomId, {
|
||||
id: roomId,
|
||||
participants: new Set(),
|
||||
});
|
||||
}
|
||||
|
||||
const room = rooms.get(roomId)!;
|
||||
room.participants.add(userId);
|
||||
|
||||
// Сохранить пользователя
|
||||
users.set(userId, {
|
||||
id: userId,
|
||||
roomId,
|
||||
socketId: socket.id,
|
||||
});
|
||||
|
||||
console.log(
|
||||
`[WebRTC] Room ${roomId} now has participants:`,
|
||||
Array.from(room.participants)
|
||||
);
|
||||
|
||||
// Уведомить других участников
|
||||
socket.to(roomId).emit("user-joined", userId);
|
||||
console.log(
|
||||
`[WebRTC] Notified room ${roomId} about user ${userId} joining`
|
||||
);
|
||||
|
||||
// Отправить состояние аудио/видео нового пользователя всем в комнате
|
||||
socket
|
||||
.to(roomId)
|
||||
.emit("audio-toggle", { userId, isEnabled: isAudioEnabled !== false });
|
||||
socket
|
||||
.to(roomId)
|
||||
.emit("video-toggle", { userId, isEnabled: isVideoEnabled !== false });
|
||||
|
||||
// Отправить список участников новому пользователю
|
||||
const participants = Array.from(room.participants).filter(
|
||||
(id) => id !== userId
|
||||
);
|
||||
console.log(
|
||||
`[WebRTC] Sending participant list to ${userId}:`,
|
||||
participants
|
||||
);
|
||||
socket.emit("room-participants", participants);
|
||||
}
|
||||
|
||||
const room = rooms.get(roomId)!;
|
||||
room.participants.add(userId);
|
||||
|
||||
// Сохранить пользователя
|
||||
users.set(userId, {
|
||||
id: userId,
|
||||
roomId,
|
||||
socketId: socket.id,
|
||||
});
|
||||
|
||||
console.log(
|
||||
`[WebRTC] Room ${roomId} now has participants:`,
|
||||
Array.from(room.participants)
|
||||
);
|
||||
|
||||
// Уведомить других участников
|
||||
socket.to(roomId).emit("user-joined", userId);
|
||||
console.log(
|
||||
`[WebRTC] Notified room ${roomId} about user ${userId} joining`
|
||||
);
|
||||
|
||||
// Отправить состояние аудио/видео нового пользователя всем в комнате
|
||||
socket.to(roomId).emit("audio-toggle", { userId, isEnabled: isAudioEnabled !== false });
|
||||
socket.to(roomId).emit("video-toggle", { userId, isEnabled: isVideoEnabled !== false });
|
||||
|
||||
// Отправить список участников новому пользователю
|
||||
const participants = Array.from(room.participants).filter(
|
||||
(id) => id !== userId
|
||||
);
|
||||
console.log(
|
||||
`[WebRTC] Sending participant list to ${userId}:`,
|
||||
participants
|
||||
);
|
||||
socket.emit("room-participants", participants);
|
||||
});
|
||||
);
|
||||
|
||||
// Покидание комнаты
|
||||
socket.on("leave-room", ({ roomId, userId }) => {
|
||||
@@ -215,13 +222,17 @@ io.on("connection", (socket) => {
|
||||
|
||||
// Обработка audio/video toggle
|
||||
socket.on("audio-toggle", ({ roomId, userId, isEnabled }) => {
|
||||
console.log(`[WebRTC] Audio toggle from ${userId} in room ${roomId}: ${isEnabled}`);
|
||||
console.log(
|
||||
`[WebRTC] Audio toggle from ${userId} in room ${roomId}: ${isEnabled}`
|
||||
);
|
||||
// Отправляем всем в комнате (кроме отправителя)
|
||||
socket.to(roomId).emit("audio-toggle", { userId, isEnabled });
|
||||
});
|
||||
|
||||
socket.on("video-toggle", ({ roomId, userId, isEnabled }) => {
|
||||
console.log(`[WebRTC] Video toggle from ${userId} in room ${roomId}: ${isEnabled}`);
|
||||
console.log(
|
||||
`[WebRTC] Video toggle from ${userId} in room ${roomId}: ${isEnabled}`
|
||||
);
|
||||
// Отправляем всем в комнате (кроме отправителя)
|
||||
socket.to(roomId).emit("video-toggle", { userId, isEnabled });
|
||||
});
|
||||
@@ -233,44 +244,118 @@ io.on("connection", (socket) => {
|
||||
});
|
||||
|
||||
// Обработка сообщений чата
|
||||
socket.on("chat-message", async ({ roomId, userId, content, userName }) => {
|
||||
console.log(`[Chat] Message from ${userId} in room ${roomId}: ${content}`);
|
||||
|
||||
const user = findUserBySocketId(socket.id);
|
||||
if (!user || user.roomId !== roomId) {
|
||||
console.warn(`[Chat] User ${socket.id} is not in room ${roomId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Сохраняем сообщение в БД
|
||||
const savedMessage = await saveChatMessage({
|
||||
sessionId: roomId,
|
||||
userId: userId || null, // null для анонимных пользователей
|
||||
socket.on(
|
||||
"chat-message",
|
||||
async ({ roomId, userId, content, senderName, guestId }) => {
|
||||
console.log(`[Chat] Received message:`, {
|
||||
roomId,
|
||||
userId,
|
||||
guestId,
|
||||
senderName,
|
||||
content,
|
||||
type: "text",
|
||||
});
|
||||
|
||||
// Формируем сообщение для отправки клиентам
|
||||
const messageToSend = {
|
||||
id: savedMessage.id,
|
||||
senderId: userId,
|
||||
senderName: userName,
|
||||
content: savedMessage.content,
|
||||
timestamp: savedMessage.createdAt,
|
||||
type: savedMessage.type,
|
||||
};
|
||||
const user = findUserBySocketId(socket.id);
|
||||
if (!user || user.roomId !== roomId) {
|
||||
console.warn(`[Chat] User ${socket.id} is not in room ${roomId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Отправляем всем в комнате (включая отправителя)
|
||||
io.to(roomId).emit("chat-message", messageToSend);
|
||||
console.log(`[Chat] Message broadcasted to room ${roomId}`);
|
||||
} catch (error) {
|
||||
console.error(`[Chat] Error saving message:`, error);
|
||||
socket.emit("chat-error", {
|
||||
message: "Failed to save message",
|
||||
});
|
||||
try {
|
||||
// Определяем имя отправителя
|
||||
const finalSenderName = senderName || "Guest";
|
||||
|
||||
console.log(
|
||||
`[Chat] Preparing to save message with senderName: "${finalSenderName}"`
|
||||
);
|
||||
|
||||
// Валидация UUID
|
||||
const uuidRegex =
|
||||
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
||||
|
||||
if (!uuidRegex.test(roomId)) {
|
||||
console.error(`[Chat] Invalid roomId/sessionId format: ${roomId}`);
|
||||
throw new Error("Invalid sessionId format");
|
||||
}
|
||||
|
||||
if (userId && !uuidRegex.test(userId)) {
|
||||
console.error(`[Chat] Invalid userId format: ${userId}`);
|
||||
throw new Error("Invalid userId format");
|
||||
}
|
||||
|
||||
if (guestId && !uuidRegex.test(guestId)) {
|
||||
console.error(`[Chat] Invalid guestId format: ${guestId}`);
|
||||
throw new Error("Invalid guestId format");
|
||||
}
|
||||
|
||||
// Проверяем существование сессии
|
||||
console.log(`[Chat] Checking if session exists: ${roomId}`);
|
||||
try {
|
||||
const sessionExists = await serverSessionService.findById(roomId);
|
||||
if (!sessionExists) {
|
||||
console.error(`[Chat] Session not found in database: ${roomId}`);
|
||||
console.error(
|
||||
`[Chat] This might cause foreign key constraint error`
|
||||
);
|
||||
// Не возвращаемся - попробуем сохранить и увидим точную ошибку БД
|
||||
} else {
|
||||
console.log(`[Chat] Session found:`, sessionExists.id);
|
||||
}
|
||||
} catch (sessionCheckError) {
|
||||
console.error(`[Chat] Error checking session:`, sessionCheckError);
|
||||
// Продолжаем - увидим точную ошибку при сохранении
|
||||
}
|
||||
|
||||
// Сохраняем сообщение в БД
|
||||
const messageData = {
|
||||
sessionId: roomId,
|
||||
userId: userId || null, // null для анонимных пользователей
|
||||
guestId: guestId || null, // ID гостя для неавторизованных
|
||||
senderName: finalSenderName, // Имя отправителя
|
||||
content,
|
||||
type: "text" as const,
|
||||
};
|
||||
|
||||
console.log(
|
||||
`[Chat] Saving message to DB:`,
|
||||
JSON.stringify(messageData, null, 2)
|
||||
);
|
||||
const savedMessage = await saveChatMessage(messageData);
|
||||
console.log(`[Chat] Message saved successfully:`, savedMessage);
|
||||
|
||||
// Формируем сообщение для отправки клиентам
|
||||
const messageToSend = {
|
||||
id: savedMessage.id,
|
||||
senderId: userId || guestId,
|
||||
senderName: savedMessage.senderName,
|
||||
content: savedMessage.content,
|
||||
timestamp: savedMessage.createdAt,
|
||||
type: savedMessage.type,
|
||||
};
|
||||
|
||||
// Отправляем всем в комнате (включая отправителя)
|
||||
io.to(roomId).emit("chat-message", messageToSend);
|
||||
console.log(`[Chat] Message broadcasted to room ${roomId}`);
|
||||
} catch (error) {
|
||||
console.error(`[Chat] Error saving message:`, error);
|
||||
console.error(`[Chat] Error details:`, JSON.stringify(error, null, 2));
|
||||
|
||||
// Отправляем детальную ошибку на клиент
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : "Failed to save message";
|
||||
const errorDetails =
|
||||
error && typeof error === "object"
|
||||
? (error as any).detail || (error as any).message || errorMessage
|
||||
: errorMessage;
|
||||
|
||||
console.error(`[Chat] Sending error to client:`, errorDetails);
|
||||
|
||||
socket.emit("chat-error", {
|
||||
message: errorDetails,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
// Отключение
|
||||
socket.on("disconnect", () => {
|
||||
|
||||
@@ -3,17 +3,24 @@ import {
|
||||
chatMessages,
|
||||
type NewChatMessage,
|
||||
} from "../../db/schema/chatMessages";
|
||||
import { eq, desc } from "drizzle-orm";
|
||||
import { eq, desc, sql } from "drizzle-orm";
|
||||
|
||||
/**
|
||||
* Сохранить новое сообщение в чате
|
||||
*/
|
||||
export async function saveChatMessage(message: NewChatMessage) {
|
||||
const [newMessage] = await db
|
||||
.insert(chatMessages)
|
||||
.values(message)
|
||||
.returning();
|
||||
return newMessage;
|
||||
try {
|
||||
console.log("[saveChatMessage] Attempting to insert message:", message);
|
||||
const [newMessage] = await db
|
||||
.insert(chatMessages)
|
||||
.values(message)
|
||||
.returning();
|
||||
console.log("[saveChatMessage] Message inserted successfully:", newMessage);
|
||||
return newMessage;
|
||||
} catch (error) {
|
||||
console.error("[saveChatMessage] Database error:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -23,7 +30,15 @@ export async function saveChatMessage(message: NewChatMessage) {
|
||||
*/
|
||||
export async function getChatHistory(sessionId: string, limit = 100) {
|
||||
const messages = await db
|
||||
.select()
|
||||
.select({
|
||||
id: chatMessages.id,
|
||||
// senderId - это либо userId (для авторизованных), либо guestId (для гостей)
|
||||
senderId: sql<string>`COALESCE(${chatMessages.userId}, ${chatMessages.guestId})`.as('senderId'),
|
||||
senderName: chatMessages.senderName,
|
||||
content: chatMessages.content,
|
||||
timestamp: chatMessages.createdAt,
|
||||
type: chatMessages.type,
|
||||
})
|
||||
.from(chatMessages)
|
||||
.where(eq(chatMessages.sessionId, sessionId))
|
||||
.orderBy(desc(chatMessages.createdAt))
|
||||
|
||||
Reference in New Issue
Block a user