import { query } from '../database/connection'; import { Message } from '../models/Message'; import { Match } from '../models/Match'; import { ProfileService } from './profileService'; import { v4 as uuidv4 } from 'uuid'; export class ChatService { private profileService: ProfileService; constructor() { this.profileService = new ProfileService(); } // Получить все чаты (матчи) пользователя async getUserChats(telegramId: string): Promise { try { // Сначала получаем userId по telegramId const userId = await this.profileService.getUserIdByTelegramId(telegramId); if (!userId) { return []; } const result = await query(` SELECT m.*, CASE WHEN m.user_id_1 = $1 THEN m.user_id_2 ELSE m.user_id_1 END as other_user_id, p.name as other_user_name, p.photos as other_user_photos, msg.content as last_message_content, msg.created_at as last_message_time, msg.sender_id as last_message_sender_id, ( SELECT COUNT(*) FROM messages msg2 WHERE msg2.match_id = m.id AND msg2.sender_id != $1 AND msg2.is_read = false ) as unread_count FROM matches m LEFT JOIN profiles p ON ( CASE WHEN m.user_id_1 = $1 THEN p.user_id = m.user_id_2 ELSE p.user_id = m.user_id_1 END ) LEFT JOIN messages msg ON msg.id = ( SELECT id FROM messages WHERE match_id = m.id ORDER BY created_at DESC LIMIT 1 ) WHERE (m.user_id_1 = $1 OR m.user_id_2 = $1) AND m.is_active = true ORDER BY CASE WHEN msg.created_at IS NULL THEN m.created_at ELSE msg.created_at END DESC `, [userId]); return result.rows.map((row: any) => ({ matchId: row.id, otherUserId: row.other_user_id, otherUserName: row.other_user_name, otherUserPhoto: row.other_user_photos?.[0] || null, lastMessage: row.last_message_content, lastMessageTime: row.last_message_time || row.matched_at, lastMessageFromMe: row.last_message_sender_id === userId, unreadCount: parseInt(row.unread_count) || 0, matchedAt: row.matched_at })); } catch (error) { console.error('Error getting user chats:', error); return []; } } // Получить сообщения в чате async getChatMessages(matchId: string, limit: number = 50, offset: number = 0): Promise { try { const result = await query(` SELECT * FROM messages WHERE match_id = $1 ORDER BY created_at DESC LIMIT $2 OFFSET $3 `, [matchId, limit, offset]); return result.rows.map((row: any) => new Message({ id: row.id, matchId: row.match_id, senderId: row.sender_id, content: row.content, messageType: row.message_type, isRead: row.is_read, createdAt: new Date(row.created_at) })).reverse(); // Возвращаем в хронологическом порядке } catch (error) { console.error('Error getting chat messages:', error); return []; } } // Отправить сообщение async sendMessage( matchId: string, senderTelegramId: string, content: string, messageType: 'text' | 'photo' | 'video' | 'voice' | 'sticker' | 'gif' = 'text' ): Promise { try { // Получаем senderId по telegramId const senderId = await this.profileService.getUserIdByTelegramId(senderTelegramId); if (!senderId) { throw new Error('Sender not found'); } // Проверяем, что матч активен и пользователь является участником const matchResult = await query(` SELECT * FROM matches WHERE id = $1 AND (user_id_1 = $2 OR user_id_2 = $2) AND is_active = true `, [matchId, senderId]); if (matchResult.rows.length === 0) { throw new Error('Match not found or not accessible'); } const messageId = uuidv4(); // Создаем сообщение await query(` INSERT INTO messages (id, match_id, sender_id, content, message_type, is_read, created_at) VALUES ($1, $2, $3, $4, $5, false, CURRENT_TIMESTAMP) `, [messageId, matchId, senderId, content, messageType]); // Обновляем время последнего сообщения в матче await query(` UPDATE matches SET last_message_at = CURRENT_TIMESTAMP WHERE id = $1 `, [matchId]); // Получаем созданное сообщение const messageResult = await query(` SELECT * FROM messages WHERE id = $1 `, [messageId]); if (messageResult.rows.length === 0) { return null; } const row = messageResult.rows[0]; return new Message({ id: row.id, matchId: row.match_id, senderId: row.sender_id, content: row.content, messageType: row.message_type, isRead: row.is_read, createdAt: new Date(row.created_at) }); } catch (error) { console.error('Error sending message:', error); return null; } } // Отметить сообщения как прочитанные async markMessagesAsRead(matchId: string, readerTelegramId: string): Promise { try { const readerId = await this.profileService.getUserIdByTelegramId(readerTelegramId); if (!readerId) { return; } await query(` UPDATE messages SET is_read = true WHERE match_id = $1 AND sender_id != $2 AND is_read = false `, [matchId, readerId]); } catch (error) { console.error('Error marking messages as read:', error); } } // Получить информацию о матче async getMatchInfo(matchId: string, userTelegramId: string): Promise { try { const userId = await this.profileService.getUserIdByTelegramId(userTelegramId); if (!userId) { return null; } const result = await query(` SELECT m.*, CASE WHEN m.user_id_1 = $2 THEN m.user_id_2 ELSE m.user_id_1 END as other_user_id FROM matches m WHERE m.id = $1 AND (m.user_id_1 = $2 OR m.user_id_2 = $2) AND m.is_active = true `, [matchId, userId]); if (result.rows.length === 0) { return null; } const match = result.rows[0]; const otherUserProfile = await this.profileService.getProfileByUserId(match.other_user_id); return { matchId: match.id, otherUserId: match.other_user_id, otherUserProfile, matchedAt: match.matched_at }; } catch (error) { console.error('Error getting match info:', error); return null; } } // Удалить матч (размэтчиться) async unmatch(matchId: string, userTelegramId: string): Promise { try { const userId = await this.profileService.getUserIdByTelegramId(userTelegramId); if (!userId) { return false; } // Проверяем, что пользователь является участником матча const matchResult = await query(` SELECT * FROM matches WHERE id = $1 AND (user_id_1 = $2 OR user_id_2 = $2) AND is_active = true `, [matchId, userId]); if (matchResult.rows.length === 0) { return false; } // Помечаем матч как неактивный await query(` UPDATE matches SET is_active = false, unmatched_at = NOW(), unmatched_by = $2 WHERE id = $1 `, [matchId, userId]); return true; } catch (error) { console.error('Error unmatching:', error); return false; } } }