Update environment configuration for production, refactor WebRTC components, and enhance chat functionality. Replace deprecated SessionUsersPanel with SessionPage, integrate chat history loading, and improve audio/video toggle handling. Remove unused SessionUsersPanel2 component and update related socket event handling in the server.

This commit is contained in:
2025-10-27 16:49:52 +05:00
parent 95f7b90d38
commit 2378ed1ff4
20 changed files with 936 additions and 304 deletions
+3 -1
View File
@@ -1,2 +1,4 @@
DATABASE_URL=postgres://postgres:v1sq3vD5faXL@194.26.138.94:5432/stream
JWT_SECRET=b5cf2bd3894fb24191f13dc9dddaeecccc92d0ee298e7ee41c2d0aab51c28fa1
JWT_SECRET=b5cf2bd3894fb24191f13dc9dddaeecccc92d0ee298e7ee41c2d0aab51c28fa1
PORT=6000
SOCKET_PORT=6001
+8
View File
@@ -0,0 +1,8 @@
module.exports = {
apps: [
{
name: "stream.graff.estate-server",
script: "bun ./dist",
},
],
};
+38
View File
@@ -0,0 +1,38 @@
import { Elysia, t } from "elysia";
import { getChatHistory } from "../services/chat";
export const chatController = new Elysia({ prefix: "/sessions" })
.get(
"/:id/messages",
async ({ params, query }) => {
const { id } = params;
const limit = query.limit ? parseInt(query.limit) : 100;
try {
const messages = await getChatHistory(id, limit);
return {
success: true,
messages,
count: messages.length,
};
} catch (error) {
console.error("[Chat API] Error fetching chat history:", error);
return {
success: false,
error: "Failed to fetch chat history",
messages: [],
count: 0,
};
}
},
{
params: t.Object({
id: t.String(),
}),
query: t.Object({
limit: t.Optional(t.String()),
}),
}
);
+42
View File
@@ -0,0 +1,42 @@
import { pgTable, uuid, text, timestamp, pgEnum } from "drizzle-orm/pg-core";
import { relations } from "drizzle-orm";
import { createInsertSchema, createSelectSchema } from "drizzle-zod";
import { serverSessions } from "./serverSessions";
import { users } from "./users";
// Enum для типа сообщения
export const messageTypeEnum = pgEnum("message_type", ["text", "system"]);
export const chatMessages = pgTable("chat_messages", {
id: uuid("id").primaryKey().defaultRandom(),
sessionId: uuid("session_id")
.notNull()
.references(() => serverSessions.id, { onDelete: "cascade" }),
userId: uuid("user_id").references(() => users.id), // Nullable для системных сообщений или анонимных пользователей
content: text("content").notNull(),
type: messageTypeEnum("type").notNull().default("text"),
createdAt: timestamp("created_at", { withTimezone: true })
.defaultNow()
.notNull(),
});
// Relations
export const chatMessagesRelations = relations(chatMessages, ({ one }) => ({
session: one(serverSessions, {
fields: [chatMessages.sessionId],
references: [serverSessions.id],
}),
user: one(users, {
fields: [chatMessages.userId],
references: [users.id],
}),
}));
// Zod schemas for validation
export const insertChatMessageSchema = createInsertSchema(chatMessages);
export const selectChatMessageSchema = createSelectSchema(chatMessages);
// Type exports
export type ChatMessage = typeof chatMessages.$inferSelect;
export type NewChatMessage = typeof chatMessages.$inferInsert;
+1
View File
@@ -9,6 +9,7 @@ export * from "./userBranches";
export * from "./serverSessions";
export * from "./authSessions";
export * from "./protectedRoutes";
export * from "./chatMessages";
// Relations (defined here to avoid circular dependencies)
import { relations } from "drizzle-orm";
+89 -9
View File
@@ -5,9 +5,12 @@ import { sessionController } from "./controllers/session";
import { companyController } from "./controllers/company";
import { branchController } from "./controllers/branch";
import { serverController } from "./controllers/server";
import { chatController } from "./controllers/chat";
import { serverSessionService } from "./services/serverSession";
import { saveChatMessage } from "./services/chat";
import { Server } from "socket.io";
import { createServer } from "http";
import { AddressInfo } from "net";
const app = new Elysia();
@@ -23,8 +26,9 @@ app.use(sessionController);
app.use(companyController);
app.use(branchController);
app.use(serverController);
app.use(chatController);
app.listen(3000);
app.listen(process.env.PORT || 3000);
console.log(
`🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`
@@ -38,8 +42,12 @@ const io = new Server(httpServer, {
},
});
httpServer.listen(3001, () => {
console.log("🎥 WebRTC Socket.IO server running on port 3001");
httpServer.listen(process.env.SOCKET_PORT || 3001, () => {
console.log(
`🎥 WebRTC Socket.IO server running on port ${
(httpServer.address() as AddressInfo).port
}`
);
});
interface Room {
@@ -75,8 +83,10 @@ io.on("connection", (socket) => {
console.log(`[WebRTC] User connected: ${socket.id}`);
// Присоединение к комнате
socket.on("join-room", ({ roomId, userId }) => {
console.log(`[WebRTC] User ${userId} (socket: ${socket.id}) joining room ${roomId}`);
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);
@@ -121,20 +131,29 @@ io.on("connection", (socket) => {
// Уведомить других участников
socket.to(roomId).emit("user-joined", userId);
console.log(`[WebRTC] Notified room ${roomId} about user ${userId} joining`);
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);
console.log(
`[WebRTC] Sending participant list to ${userId}:`,
participants
);
socket.emit("room-participants", participants);
});
// Покидание комнаты
socket.on("leave-room", ({ roomId, userId }) => {
console.log(`[WebRTC] User ${userId} leaving room ${roomId}`);
socket.leave(roomId);
const room = rooms.get(roomId);
if (room) {
@@ -194,6 +213,65 @@ 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}`);
// Отправляем всем в комнате (кроме отправителя)
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}`);
// Отправляем всем в комнате (кроме отправителя)
socket.to(roomId).emit("video-toggle", { userId, isEnabled });
});
// Обработка speaking state
socket.on("speaking-state", ({ roomId, userId, isSpeaking }) => {
// Отправляем всем в комнате (кроме отправителя)
socket.to(roomId).emit("speaking-state", { userId, isSpeaking });
});
// Обработка сообщений чата
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 для анонимных пользователей
content,
type: "text",
});
// Формируем сообщение для отправки клиентам
const messageToSend = {
id: savedMessage.id,
senderId: userId,
senderName: userName,
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);
socket.emit("chat-error", {
message: "Failed to save message",
});
}
});
// Отключение
socket.on("disconnect", () => {
console.log(`[WebRTC] User disconnected: ${socket.id}`);
@@ -215,7 +293,9 @@ io.on("connection", (socket) => {
// Удалить пустую комнату
if (room.participants.size === 0) {
rooms.delete(disconnectedUser.roomId);
console.log(`[WebRTC] Deleted empty room ${disconnectedUser.roomId}`);
console.log(
`[WebRTC] Deleted empty room ${disconnectedUser.roomId}`
);
}
}
}
+42
View File
@@ -0,0 +1,42 @@
import db from "../../db";
import {
chatMessages,
type NewChatMessage,
} from "../../db/schema/chatMessages";
import { eq, desc } from "drizzle-orm";
/**
* Сохранить новое сообщение в чате
*/
export async function saveChatMessage(message: NewChatMessage) {
const [newMessage] = await db
.insert(chatMessages)
.values(message)
.returning();
return newMessage;
}
/**
* Получить историю сообщений для сессии
* @param sessionId ID сессии
* @param limit Максимальное количество сообщений (по умолчанию 100)
*/
export async function getChatHistory(sessionId: string, limit = 100) {
const messages = await db
.select()
.from(chatMessages)
.where(eq(chatMessages.sessionId, sessionId))
.orderBy(desc(chatMessages.createdAt))
.limit(limit);
// Возвращаем в правильном порядке (старые сначала)
return messages.reverse();
}
/**
* Удалить все сообщения для сессии
* @param sessionId ID сессии
*/
export async function deleteChatHistory(sessionId: string) {
await db.delete(chatMessages).where(eq(chatMessages.sessionId, sessionId));
}