add chats api
This commit is contained in:
@@ -3,12 +3,33 @@ const { getSupabaseClient } = require('./supabaseClient');
|
|||||||
|
|
||||||
// Получить все чаты по дому
|
// Получить все чаты по дому
|
||||||
router.get('/chats', async (req, res) => {
|
router.get('/chats', async (req, res) => {
|
||||||
|
console.log('🏠 [Server] GET /chats запрос получен');
|
||||||
|
console.log('🏠 [Server] Query параметры:', req.query);
|
||||||
|
|
||||||
const supabase = getSupabaseClient();
|
const supabase = getSupabaseClient();
|
||||||
const { building_id } = req.query;
|
const { building_id } = req.query;
|
||||||
if (!building_id) return res.status(400).json({ error: 'building_id required' });
|
|
||||||
|
if (!building_id) {
|
||||||
|
console.log('❌ [Server] Ошибка: building_id обязателен');
|
||||||
|
return res.status(400).json({ error: 'building_id required' });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log('🔍 [Server] Выполняем запрос к Supabase для здания:', building_id);
|
||||||
|
|
||||||
const { data, error } = await supabase.from('chats').select('*').eq('building_id', building_id);
|
const { data, error } = await supabase.from('chats').select('*').eq('building_id', building_id);
|
||||||
if (error) return res.status(400).json({ error: error.message });
|
|
||||||
res.json(data);
|
if (error) {
|
||||||
|
console.log('❌ [Server] Ошибка Supabase:', error);
|
||||||
|
return res.status(400).json({ error: error.message });
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('✅ [Server] Чаты получены:', data?.length || 0, 'шт.');
|
||||||
|
res.json(data || []);
|
||||||
|
} catch (err) {
|
||||||
|
console.log('❌ [Server] Неожиданная ошибка:', err);
|
||||||
|
res.status(500).json({ error: 'Internal server error' });
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Получить все чаты по квартире (через building_id)
|
// Получить все чаты по квартире (через building_id)
|
||||||
@@ -25,4 +46,191 @@ router.get('/chats/by-apartment', async (req, res) => {
|
|||||||
res.json(data);
|
res.json(data);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Создать новый чат
|
||||||
|
router.post('/chats', async (req, res) => {
|
||||||
|
const supabase = getSupabaseClient();
|
||||||
|
const { building_id, name } = req.body;
|
||||||
|
|
||||||
|
if (!building_id) {
|
||||||
|
return res.status(400).json({ error: 'building_id is required' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data, error } = await supabase
|
||||||
|
.from('chats')
|
||||||
|
.insert({ building_id, name })
|
||||||
|
.select()
|
||||||
|
.single();
|
||||||
|
|
||||||
|
if (error) return res.status(400).json({ error: error.message });
|
||||||
|
res.json(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Получить конкретный чат по ID
|
||||||
|
router.get('/chats/:chat_id', async (req, res) => {
|
||||||
|
const supabase = getSupabaseClient();
|
||||||
|
const { chat_id } = req.params;
|
||||||
|
|
||||||
|
const { data, error } = await supabase
|
||||||
|
.from('chats')
|
||||||
|
.select('*')
|
||||||
|
.eq('id', chat_id)
|
||||||
|
.single();
|
||||||
|
|
||||||
|
if (error) return res.status(400).json({ error: error.message });
|
||||||
|
res.json(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Обновить чат
|
||||||
|
router.put('/chats/:chat_id', async (req, res) => {
|
||||||
|
const supabase = getSupabaseClient();
|
||||||
|
const { chat_id } = req.params;
|
||||||
|
const { name } = req.body;
|
||||||
|
|
||||||
|
const { data, error } = await supabase
|
||||||
|
.from('chats')
|
||||||
|
.update({ name })
|
||||||
|
.eq('id', chat_id)
|
||||||
|
.select()
|
||||||
|
.single();
|
||||||
|
|
||||||
|
if (error) return res.status(400).json({ error: error.message });
|
||||||
|
res.json(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Удалить чат
|
||||||
|
router.delete('/chats/:chat_id', async (req, res) => {
|
||||||
|
const supabase = getSupabaseClient();
|
||||||
|
const { chat_id } = req.params;
|
||||||
|
|
||||||
|
const { error } = await supabase
|
||||||
|
.from('chats')
|
||||||
|
.delete()
|
||||||
|
.eq('id', chat_id);
|
||||||
|
|
||||||
|
if (error) return res.status(400).json({ error: error.message });
|
||||||
|
res.json({ success: true, message: 'Chat deleted successfully' });
|
||||||
|
});
|
||||||
|
|
||||||
|
// Получить статистику чата (количество сообщений, участников и т.д.)
|
||||||
|
router.get('/chats/:chat_id/stats', async (req, res) => {
|
||||||
|
const supabase = getSupabaseClient();
|
||||||
|
const { chat_id } = req.params;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Получаем количество сообщений
|
||||||
|
const { count: messageCount, error: messageError } = await supabase
|
||||||
|
.from('messages')
|
||||||
|
.select('*', { count: 'exact', head: true })
|
||||||
|
.eq('chat_id', chat_id);
|
||||||
|
|
||||||
|
if (messageError) throw messageError;
|
||||||
|
|
||||||
|
// Получаем информацию о чате с домом
|
||||||
|
const { data: chatInfo, error: chatError } = await supabase
|
||||||
|
.from('chats')
|
||||||
|
.select(`
|
||||||
|
*,
|
||||||
|
buildings (
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
address,
|
||||||
|
apartments (
|
||||||
|
apartment_residents (
|
||||||
|
user_id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
`)
|
||||||
|
.eq('id', chat_id)
|
||||||
|
.single();
|
||||||
|
|
||||||
|
if (chatError) throw chatError;
|
||||||
|
|
||||||
|
// Собираем уникальные user_id жителей дома
|
||||||
|
const userIds = new Set();
|
||||||
|
chatInfo.buildings.apartments.forEach(apartment => {
|
||||||
|
apartment.apartment_residents.forEach(resident => {
|
||||||
|
userIds.add(resident.user_id);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Получаем профили всех жителей
|
||||||
|
let uniqueResidents = [];
|
||||||
|
if (userIds.size > 0) {
|
||||||
|
const { data: profiles } = await supabase
|
||||||
|
.from('user_profiles')
|
||||||
|
.select('id, full_name, avatar_url')
|
||||||
|
.in('id', Array.from(userIds));
|
||||||
|
|
||||||
|
uniqueResidents = profiles || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
chat_id,
|
||||||
|
chat_name: chatInfo.name,
|
||||||
|
building: {
|
||||||
|
id: chatInfo.buildings.id,
|
||||||
|
name: chatInfo.buildings.name,
|
||||||
|
address: chatInfo.buildings.address
|
||||||
|
},
|
||||||
|
message_count: messageCount || 0,
|
||||||
|
total_residents: uniqueResidents.length,
|
||||||
|
residents: uniqueResidents
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
res.status(400).json({ error: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Получить последнее сообщение в чате
|
||||||
|
router.get('/chats/:chat_id/last-message', async (req, res) => {
|
||||||
|
console.log('💬 [Server] GET /chats/:chat_id/last-message запрос получен');
|
||||||
|
console.log('💬 [Server] Chat ID:', req.params.chat_id);
|
||||||
|
|
||||||
|
const supabase = getSupabaseClient();
|
||||||
|
const { chat_id } = req.params;
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log('🔍 [Server] Выполняем запрос последнего сообщения для чата:', chat_id);
|
||||||
|
|
||||||
|
// Получаем последнее сообщение
|
||||||
|
const { data: lastMessage, error } = await supabase
|
||||||
|
.from('messages')
|
||||||
|
.select('*')
|
||||||
|
.eq('chat_id', chat_id)
|
||||||
|
.order('created_at', { ascending: false })
|
||||||
|
.limit(1)
|
||||||
|
.single();
|
||||||
|
|
||||||
|
let data = null;
|
||||||
|
|
||||||
|
if (error && error.code === 'PGRST116') {
|
||||||
|
console.log('ℹ️ [Server] Сообщений в чате нет (PGRST116)');
|
||||||
|
data = null;
|
||||||
|
} else if (error) {
|
||||||
|
console.log('❌ [Server] Ошибка Supabase при получении последнего сообщения:', error);
|
||||||
|
return res.status(400).json({ error: error.message });
|
||||||
|
} else if (lastMessage) {
|
||||||
|
// Получаем профиль пользователя для сообщения
|
||||||
|
const { data: userProfile } = await supabase
|
||||||
|
.from('user_profiles')
|
||||||
|
.select('id, full_name, avatar_url')
|
||||||
|
.eq('id', lastMessage.user_id)
|
||||||
|
.single();
|
||||||
|
|
||||||
|
// Объединяем сообщение с профилем
|
||||||
|
data = {
|
||||||
|
...lastMessage,
|
||||||
|
user_profiles: userProfile || null
|
||||||
|
};
|
||||||
|
console.log('✅ [Server] Последнее сообщение получено для чата:', chat_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json(data);
|
||||||
|
} catch (err) {
|
||||||
|
console.log('❌ [Server] Неожиданная ошибка при получении последнего сообщения:', err);
|
||||||
|
res.status(500).json({ error: 'Internal server error' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
@@ -1,14 +1,210 @@
|
|||||||
const router = require('express').Router();
|
const router = require('express').Router();
|
||||||
const { getSupabaseClient } = require('./supabaseClient');
|
const { getSupabaseClient } = require('./supabaseClient');
|
||||||
|
|
||||||
// Получить все сообщения в чате
|
// Получить все сообщения в чате с информацией о пользователе
|
||||||
router.get('/messages', async (req, res) => {
|
router.get('/messages', async (req, res) => {
|
||||||
|
console.log('📬 [Server] GET /messages запрос получен');
|
||||||
|
console.log('📬 [Server] Query параметры:', req.query);
|
||||||
|
|
||||||
const supabase = getSupabaseClient();
|
const supabase = getSupabaseClient();
|
||||||
const { chat_id } = req.query;
|
const { chat_id, limit = 50, offset = 0 } = req.query;
|
||||||
if (!chat_id) return res.status(400).json({ error: 'chat_id required' });
|
|
||||||
const { data, error } = await supabase.from('messages').select('*').eq('chat_id', chat_id);
|
if (!chat_id) {
|
||||||
|
console.log('❌ [Server] Ошибка: chat_id обязателен');
|
||||||
|
return res.status(400).json({ error: 'chat_id required' });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log('🔍 [Server] Выполняем запрос к Supabase для чата:', chat_id);
|
||||||
|
|
||||||
|
// Получаем сообщения
|
||||||
|
const { data: messages, error } = await supabase
|
||||||
|
.from('messages')
|
||||||
|
.select('*')
|
||||||
|
.eq('chat_id', chat_id)
|
||||||
|
.order('created_at', { ascending: false })
|
||||||
|
.limit(limit)
|
||||||
|
.range(offset, offset + limit - 1);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
console.log('❌ [Server] Ошибка получения сообщений:', error);
|
||||||
|
return res.status(400).json({ error: error.message });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Получаем профили пользователей для всех уникальных user_id
|
||||||
|
let data = messages || [];
|
||||||
|
if (data.length > 0) {
|
||||||
|
const userIds = [...new Set(data.map(msg => msg.user_id))];
|
||||||
|
console.log('👥 [Server] Получаем профили для пользователей:', userIds);
|
||||||
|
|
||||||
|
const { data: profiles, error: profilesError } = await supabase
|
||||||
|
.from('user_profiles')
|
||||||
|
.select('id, full_name, avatar_url')
|
||||||
|
.in('id', userIds);
|
||||||
|
|
||||||
|
if (!profilesError && profiles) {
|
||||||
|
// Объединяем сообщения с профилями
|
||||||
|
data = data.map(msg => ({
|
||||||
|
...msg,
|
||||||
|
user_profiles: profiles.find(profile => profile.id === msg.user_id) || null
|
||||||
|
}));
|
||||||
|
console.log('✅ [Server] Профили пользователей добавлены к сообщениям');
|
||||||
|
} else {
|
||||||
|
console.log('⚠️ [Server] Ошибка получения профилей пользователей:', profilesError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('✅ [Server] Сообщения получены:', data?.length || 0, 'шт.');
|
||||||
|
res.json(data?.reverse() || []); // Возвращаем в хронологическом порядке
|
||||||
|
} catch (err) {
|
||||||
|
console.log('❌ [Server] Неожиданная ошибка:', err);
|
||||||
|
res.status(500).json({ error: 'Internal server error' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Создать новое сообщение
|
||||||
|
router.post('/messages', async (req, res) => {
|
||||||
|
const supabase = getSupabaseClient();
|
||||||
|
const { chat_id, user_id, text } = req.body;
|
||||||
|
|
||||||
|
if (!chat_id || !user_id || !text) {
|
||||||
|
return res.status(400).json({
|
||||||
|
error: 'chat_id, user_id, and text are required'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Создаем сообщение
|
||||||
|
const { data: newMessage, error } = await supabase
|
||||||
|
.from('messages')
|
||||||
|
.insert({ chat_id, user_id, text })
|
||||||
|
.select('*')
|
||||||
|
.single();
|
||||||
|
|
||||||
if (error) return res.status(400).json({ error: error.message });
|
if (error) return res.status(400).json({ error: error.message });
|
||||||
|
|
||||||
|
// Получаем профиль пользователя
|
||||||
|
const { data: userProfile } = await supabase
|
||||||
|
.from('user_profiles')
|
||||||
|
.select('id, full_name, avatar_url')
|
||||||
|
.eq('id', user_id)
|
||||||
|
.single();
|
||||||
|
|
||||||
|
// Объединяем сообщение с профилем
|
||||||
|
const data = {
|
||||||
|
...newMessage,
|
||||||
|
user_profiles: userProfile || null
|
||||||
|
};
|
||||||
|
|
||||||
res.json(data);
|
res.json(data);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Получить конкретное сообщение
|
||||||
|
router.get('/messages/:message_id', async (req, res) => {
|
||||||
|
const supabase = getSupabaseClient();
|
||||||
|
const { message_id } = req.params;
|
||||||
|
|
||||||
|
// Получаем сообщение
|
||||||
|
const { data: message, error } = await supabase
|
||||||
|
.from('messages')
|
||||||
|
.select('*')
|
||||||
|
.eq('id', message_id)
|
||||||
|
.single();
|
||||||
|
|
||||||
|
if (error) return res.status(400).json({ error: error.message });
|
||||||
|
|
||||||
|
// Получаем профиль пользователя
|
||||||
|
const { data: userProfile } = await supabase
|
||||||
|
.from('user_profiles')
|
||||||
|
.select('id, full_name, avatar_url')
|
||||||
|
.eq('id', message.user_id)
|
||||||
|
.single();
|
||||||
|
|
||||||
|
// Объединяем сообщение с профилем
|
||||||
|
const data = {
|
||||||
|
...message,
|
||||||
|
user_profiles: userProfile || null
|
||||||
|
};
|
||||||
|
|
||||||
|
res.json(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Получить последние сообщения для каждого чата (для списка чатов)
|
||||||
|
router.get('/chats/last-messages', async (req, res) => {
|
||||||
|
const supabase = getSupabaseClient();
|
||||||
|
const { building_id } = req.query;
|
||||||
|
|
||||||
|
if (!building_id) {
|
||||||
|
return res.status(400).json({ error: 'building_id required' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Получаем чаты и их последние сообщения через обычные запросы
|
||||||
|
const { data: chats, error: chatsError } = await supabase
|
||||||
|
.from('chats')
|
||||||
|
.select('*')
|
||||||
|
.eq('building_id', building_id);
|
||||||
|
|
||||||
|
if (chatsError) return res.status(400).json({ error: chatsError.message });
|
||||||
|
|
||||||
|
// Для каждого чата получаем последнее сообщение
|
||||||
|
const chatsWithMessages = await Promise.all(
|
||||||
|
chats.map(async (chat) => {
|
||||||
|
const { data: lastMessage } = await supabase
|
||||||
|
.from('messages')
|
||||||
|
.select(`
|
||||||
|
*,
|
||||||
|
user_profiles:user_id (
|
||||||
|
id,
|
||||||
|
full_name,
|
||||||
|
avatar_url
|
||||||
|
)
|
||||||
|
`)
|
||||||
|
.eq('chat_id', chat.id)
|
||||||
|
.order('created_at', { ascending: false })
|
||||||
|
.limit(1)
|
||||||
|
.single();
|
||||||
|
|
||||||
|
return {
|
||||||
|
...chat,
|
||||||
|
last_message: lastMessage || null
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
res.json(chatsWithMessages);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Удалить сообщение (только для автора)
|
||||||
|
router.delete('/messages/:message_id', async (req, res) => {
|
||||||
|
const supabase = getSupabaseClient();
|
||||||
|
const { message_id } = req.params;
|
||||||
|
const { user_id } = req.body;
|
||||||
|
|
||||||
|
if (!user_id) {
|
||||||
|
return res.status(400).json({ error: 'user_id required' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверяем, что пользователь является автором сообщения
|
||||||
|
const { data: message, error: fetchError } = await supabase
|
||||||
|
.from('messages')
|
||||||
|
.select('user_id')
|
||||||
|
.eq('id', message_id)
|
||||||
|
.single();
|
||||||
|
|
||||||
|
if (fetchError) return res.status(400).json({ error: fetchError.message });
|
||||||
|
|
||||||
|
if (message.user_id !== user_id) {
|
||||||
|
return res.status(403).json({ error: 'You can only delete your own messages' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const { error } = await supabase
|
||||||
|
.from('messages')
|
||||||
|
.delete()
|
||||||
|
.eq('id', message_id);
|
||||||
|
|
||||||
|
if (error) return res.status(400).json({ error: error.message });
|
||||||
|
res.json({ success: true, message: 'Message deleted successfully' });
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
340
server/routers/kfu-m-24-1/sber_mobile/socket-chat.js
Normal file
340
server/routers/kfu-m-24-1/sber_mobile/socket-chat.js
Normal file
@@ -0,0 +1,340 @@
|
|||||||
|
const { getSupabaseClient } = require('./supabaseClient');
|
||||||
|
|
||||||
|
class ChatSocketHandler {
|
||||||
|
constructor(io) {
|
||||||
|
this.io = io;
|
||||||
|
this.onlineUsers = new Map(); // Хранение онлайн пользователей: socket.id -> user info
|
||||||
|
this.chatRooms = new Map(); // Хранение участников комнат: chat_id -> Set(socket.id)
|
||||||
|
this.setupSocketHandlers();
|
||||||
|
}
|
||||||
|
|
||||||
|
setupSocketHandlers() {
|
||||||
|
this.io.on('connection', (socket) => {
|
||||||
|
console.log(`User connected: ${socket.id}`);
|
||||||
|
|
||||||
|
// Аутентификация пользователя
|
||||||
|
socket.on('authenticate', async (data) => {
|
||||||
|
await this.handleAuthentication(socket, data);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Присоединение к чату
|
||||||
|
socket.on('join_chat', async (data) => {
|
||||||
|
await this.handleJoinChat(socket, data);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Покидание чата
|
||||||
|
socket.on('leave_chat', (data) => {
|
||||||
|
this.handleLeaveChat(socket, data);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Отправка сообщения
|
||||||
|
socket.on('send_message', async (data) => {
|
||||||
|
await this.handleSendMessage(socket, data);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Пользователь начал печатать
|
||||||
|
socket.on('typing_start', (data) => {
|
||||||
|
this.handleTypingStart(socket, data);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Пользователь закончил печатать
|
||||||
|
socket.on('typing_stop', (data) => {
|
||||||
|
this.handleTypingStop(socket, data);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Отключение пользователя
|
||||||
|
socket.on('disconnect', () => {
|
||||||
|
this.handleDisconnect(socket);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleAuthentication(socket, data) {
|
||||||
|
try {
|
||||||
|
const { user_id, token } = data;
|
||||||
|
|
||||||
|
if (!user_id) {
|
||||||
|
socket.emit('auth_error', { message: 'user_id is required' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Получаем информацию о пользователе из базы данных
|
||||||
|
const supabase = getSupabaseClient();
|
||||||
|
const { data: userProfile, error } = await supabase
|
||||||
|
.from('user_profiles')
|
||||||
|
.select('*')
|
||||||
|
.eq('id', user_id)
|
||||||
|
.single();
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
socket.emit('auth_error', { message: 'User not found' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Сохраняем информацию о пользователе
|
||||||
|
this.onlineUsers.set(socket.id, {
|
||||||
|
user_id,
|
||||||
|
socket_id: socket.id,
|
||||||
|
profile: userProfile,
|
||||||
|
last_seen: new Date()
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.user_id = user_id;
|
||||||
|
socket.emit('authenticated', {
|
||||||
|
message: 'Successfully authenticated',
|
||||||
|
user: userProfile
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`User ${user_id} authenticated with socket ${socket.id}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Authentication error:', error);
|
||||||
|
socket.emit('auth_error', { message: 'Authentication failed' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleJoinChat(socket, data) {
|
||||||
|
try {
|
||||||
|
const { chat_id } = data;
|
||||||
|
|
||||||
|
if (!socket.user_id) {
|
||||||
|
socket.emit('error', { message: 'Not authenticated' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!chat_id) {
|
||||||
|
socket.emit('error', { message: 'chat_id is required' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверяем, что чат существует и пользователь имеет доступ к нему
|
||||||
|
const supabase = getSupabaseClient();
|
||||||
|
const { data: chat, error } = await supabase
|
||||||
|
.from('chats')
|
||||||
|
.select(`
|
||||||
|
*,
|
||||||
|
buildings (
|
||||||
|
management_company_id,
|
||||||
|
apartments (
|
||||||
|
apartment_residents (
|
||||||
|
user_id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
`)
|
||||||
|
.eq('id', chat_id)
|
||||||
|
.single();
|
||||||
|
|
||||||
|
if (error || !chat) {
|
||||||
|
socket.emit('error', { message: 'Chat not found' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверяем доступ пользователя к чату через квартиры в доме
|
||||||
|
const hasAccess = chat.buildings.apartments.some(apartment =>
|
||||||
|
apartment.apartment_residents.some(resident =>
|
||||||
|
resident.user_id === socket.user_id
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!hasAccess) {
|
||||||
|
socket.emit('error', { message: 'Access denied to this chat' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Добавляем сокет в комнату
|
||||||
|
socket.join(chat_id);
|
||||||
|
|
||||||
|
// Обновляем список участников комнаты
|
||||||
|
if (!this.chatRooms.has(chat_id)) {
|
||||||
|
this.chatRooms.set(chat_id, new Set());
|
||||||
|
}
|
||||||
|
this.chatRooms.get(chat_id).add(socket.id);
|
||||||
|
|
||||||
|
socket.emit('joined_chat', {
|
||||||
|
chat_id,
|
||||||
|
chat: chat,
|
||||||
|
message: 'Successfully joined chat'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Уведомляем других участников о подключении
|
||||||
|
const userInfo = this.onlineUsers.get(socket.id);
|
||||||
|
socket.to(chat_id).emit('user_joined', {
|
||||||
|
chat_id,
|
||||||
|
user: userInfo?.profile,
|
||||||
|
timestamp: new Date()
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`User ${socket.user_id} joined chat ${chat_id}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Join chat error:', error);
|
||||||
|
socket.emit('error', { message: 'Failed to join chat' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleLeaveChat(socket, data) {
|
||||||
|
const { chat_id } = data;
|
||||||
|
|
||||||
|
if (!chat_id) return;
|
||||||
|
|
||||||
|
socket.leave(chat_id);
|
||||||
|
|
||||||
|
// Удаляем из списка участников
|
||||||
|
if (this.chatRooms.has(chat_id)) {
|
||||||
|
this.chatRooms.get(chat_id).delete(socket.id);
|
||||||
|
|
||||||
|
// Если комната пуста, удаляем её
|
||||||
|
if (this.chatRooms.get(chat_id).size === 0) {
|
||||||
|
this.chatRooms.delete(chat_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Уведомляем других участников об отключении
|
||||||
|
const userInfo = this.onlineUsers.get(socket.id);
|
||||||
|
socket.to(chat_id).emit('user_left', {
|
||||||
|
chat_id,
|
||||||
|
user: userInfo?.profile,
|
||||||
|
timestamp: new Date()
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`User ${socket.user_id} left chat ${chat_id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleSendMessage(socket, data) {
|
||||||
|
try {
|
||||||
|
const { chat_id, text } = data;
|
||||||
|
|
||||||
|
if (!socket.user_id) {
|
||||||
|
socket.emit('error', { message: 'Not authenticated' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!chat_id || !text) {
|
||||||
|
socket.emit('error', { message: 'chat_id and text are required' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Сохраняем сообщение в базу данных
|
||||||
|
const supabase = getSupabaseClient();
|
||||||
|
const { data: message, error } = await supabase
|
||||||
|
.from('messages')
|
||||||
|
.insert({
|
||||||
|
chat_id,
|
||||||
|
user_id: socket.user_id,
|
||||||
|
text
|
||||||
|
})
|
||||||
|
.select(`
|
||||||
|
*,
|
||||||
|
user_profiles (
|
||||||
|
id,
|
||||||
|
full_name,
|
||||||
|
avatar_url
|
||||||
|
)
|
||||||
|
`)
|
||||||
|
.single();
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
socket.emit('error', { message: 'Failed to save message' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Отправляем сообщение всем участникам чата
|
||||||
|
this.io.to(chat_id).emit('new_message', {
|
||||||
|
message,
|
||||||
|
timestamp: new Date()
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`Message sent to chat ${chat_id} by user ${socket.user_id}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Send message error:', error);
|
||||||
|
socket.emit('error', { message: 'Failed to send message' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleTypingStart(socket, data) {
|
||||||
|
const { chat_id } = data;
|
||||||
|
|
||||||
|
if (!socket.user_id || !chat_id) return;
|
||||||
|
|
||||||
|
const userInfo = this.onlineUsers.get(socket.id);
|
||||||
|
socket.to(chat_id).emit('user_typing_start', {
|
||||||
|
chat_id,
|
||||||
|
user: userInfo?.profile,
|
||||||
|
timestamp: new Date()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleTypingStop(socket, data) {
|
||||||
|
const { chat_id } = data;
|
||||||
|
|
||||||
|
if (!socket.user_id || !chat_id) return;
|
||||||
|
|
||||||
|
const userInfo = this.onlineUsers.get(socket.id);
|
||||||
|
socket.to(chat_id).emit('user_typing_stop', {
|
||||||
|
chat_id,
|
||||||
|
user: userInfo?.profile,
|
||||||
|
timestamp: new Date()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDisconnect(socket) {
|
||||||
|
console.log(`User disconnected: ${socket.id}`);
|
||||||
|
|
||||||
|
// Удаляем пользователя из всех комнат
|
||||||
|
this.chatRooms.forEach((participants, chat_id) => {
|
||||||
|
if (participants.has(socket.id)) {
|
||||||
|
participants.delete(socket.id);
|
||||||
|
|
||||||
|
// Уведомляем других участников об отключении
|
||||||
|
const userInfo = this.onlineUsers.get(socket.id);
|
||||||
|
socket.to(chat_id).emit('user_left', {
|
||||||
|
chat_id,
|
||||||
|
user: userInfo?.profile,
|
||||||
|
timestamp: new Date()
|
||||||
|
});
|
||||||
|
|
||||||
|
// Если комната пуста, удаляем её
|
||||||
|
if (participants.size === 0) {
|
||||||
|
this.chatRooms.delete(chat_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Удаляем пользователя из списка онлайн
|
||||||
|
this.onlineUsers.delete(socket.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Получение списка онлайн пользователей в чате
|
||||||
|
getOnlineUsersInChat(chat_id) {
|
||||||
|
const participants = this.chatRooms.get(chat_id) || new Set();
|
||||||
|
const onlineUsers = [];
|
||||||
|
|
||||||
|
participants.forEach(socketId => {
|
||||||
|
const userInfo = this.onlineUsers.get(socketId);
|
||||||
|
if (userInfo) {
|
||||||
|
onlineUsers.push(userInfo.profile);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return onlineUsers;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Отправка системного сообщения в чат
|
||||||
|
async sendSystemMessage(chat_id, text) {
|
||||||
|
this.io.to(chat_id).emit('system_message', {
|
||||||
|
chat_id,
|
||||||
|
text,
|
||||||
|
timestamp: new Date()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Функция инициализации Socket.IO для чатов
|
||||||
|
function initializeChatSocket(io) {
|
||||||
|
const chatHandler = new ChatSocketHandler(io);
|
||||||
|
return chatHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
ChatSocketHandler,
|
||||||
|
initializeChatSocket
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user