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:
+3
-1
@@ -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
|
||||
@@ -0,0 +1,8 @@
|
||||
module.exports = {
|
||||
apps: [
|
||||
{
|
||||
name: "stream.graff.estate-server",
|
||||
script: "bun ./dist",
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -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()),
|
||||
}),
|
||||
}
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
Reference in New Issue
Block a user