Fix like/dislike errors and implement native chat system
This commit is contained in:
@@ -44,7 +44,7 @@ export class MatchingService {
|
||||
|
||||
// Получить профили пользователей
|
||||
const userProfile = await this.profileService.getProfileByTelegramId(telegramId);
|
||||
const targetProfile = await this.profileService.getProfileByUserId(targetTelegramId); if (!userProfile || !targetProfile) {
|
||||
const targetProfile = await this.profileService.getProfileByTelegramId(targetTelegramId); if (!userProfile || !targetProfile) {
|
||||
throw new BotError('Profile not found', 'PROFILE_NOT_FOUND', 400);
|
||||
}
|
||||
|
||||
@@ -82,26 +82,37 @@ export class MatchingService {
|
||||
`, [targetUserId, userId]);
|
||||
|
||||
if (reciprocalSwipe.rows.length > 0) {
|
||||
isMatch = true;
|
||||
const matchId = uuidv4();
|
||||
const isSuperMatch = swipeType === 'superlike' || reciprocalSwipe.rows[0].direction === 'super';
|
||||
// Проверяем, что матч еще не существует
|
||||
const existingMatch = await client.query(`
|
||||
SELECT * FROM matches
|
||||
WHERE (user1_id = $1 AND user2_id = $2) OR (user1_id = $2 AND user2_id = $1)
|
||||
`, [userId, targetUserId]);
|
||||
|
||||
// Создаем матч
|
||||
await client.query(`
|
||||
INSERT INTO matches (id, user1_id, user2_id, matched_at, status)
|
||||
VALUES ($1, $2, $3, $4, $5)
|
||||
`, [matchId, userId, targetUserId, new Date(), 'active']);
|
||||
if (existingMatch.rows.length === 0) {
|
||||
isMatch = true;
|
||||
const matchId = uuidv4();
|
||||
const isSuperMatch = swipeType === 'superlike' || reciprocalSwipe.rows[0].direction === 'super';
|
||||
|
||||
match = new Match({
|
||||
id: matchId,
|
||||
userId1: userId,
|
||||
userId2: targetUserId,
|
||||
createdAt: new Date(),
|
||||
isActive: true,
|
||||
isSuperMatch: false,
|
||||
unreadCount1: 0,
|
||||
unreadCount2: 0
|
||||
});
|
||||
// Упорядочиваем пользователей для консистентности
|
||||
const [user1Id, user2Id] = userId < targetUserId ? [userId, targetUserId] : [targetUserId, userId];
|
||||
|
||||
// Создаем матч
|
||||
await client.query(`
|
||||
INSERT INTO matches (id, user1_id, user2_id, matched_at, status)
|
||||
VALUES ($1, $2, $3, $4, $5)
|
||||
`, [matchId, user1Id, user2Id, new Date(), 'active']);
|
||||
|
||||
match = new Match({
|
||||
id: matchId,
|
||||
userId1: user1Id,
|
||||
userId2: user2Id,
|
||||
createdAt: new Date(),
|
||||
isActive: true,
|
||||
isSuperMatch: isSuperMatch,
|
||||
unreadCount1: 0,
|
||||
unreadCount2: 0
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -227,15 +227,65 @@ export class NotificationService {
|
||||
|
||||
// Проверить, активен ли пользователь в чате
|
||||
private async isUserActiveInChat(userId: string, chatWithUserId: string): Promise<boolean> {
|
||||
// TODO: Реализовать проверку активности пользователя
|
||||
// Можно использовать Redis для хранения состояния активности
|
||||
return false;
|
||||
try {
|
||||
// Проверяем последнее сообщение пользователя в чате
|
||||
const result = await query(`
|
||||
SELECT m.created_at
|
||||
FROM messages m
|
||||
JOIN matches mt ON m.match_id = mt.id
|
||||
WHERE (mt.user1_id = $1 OR mt.user2_id = $1)
|
||||
AND (mt.user1_id = $2 OR mt.user2_id = $2)
|
||||
AND m.sender_id = $1
|
||||
ORDER BY m.created_at DESC
|
||||
LIMIT 1
|
||||
`, [userId, chatWithUserId]);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
return false; // Нет сообщений - не активен
|
||||
}
|
||||
|
||||
const lastMessageTime = new Date(result.rows[0].created_at);
|
||||
const now = new Date();
|
||||
const hoursSinceLastMessage = (now.getTime() - lastMessageTime.getTime()) / (1000 * 60 * 60);
|
||||
|
||||
// Считаем активным если последнее сообщение было менее 24 часов назад
|
||||
return hoursSinceLastMessage < 24;
|
||||
} catch (error) {
|
||||
console.error('Error checking user activity:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Отправить пуш-уведомление (для будущего использования)
|
||||
async sendPushNotification(userId: string, title: string, body: string, data?: any): Promise<void> {
|
||||
// TODO: Интеграция с Firebase Cloud Messaging или другим сервисом пуш-уведомлений
|
||||
console.log(`Push notification for ${userId}: ${title} - ${body}`);
|
||||
try {
|
||||
// Логируем уведомление
|
||||
console.log(`📱 Push notification prepared for user ${userId}:`);
|
||||
console.log(`📋 Title: ${title}`);
|
||||
console.log(`💬 Body: ${body}`);
|
||||
if (data) {
|
||||
console.log(`📊 Data:`, JSON.stringify(data, null, 2));
|
||||
}
|
||||
|
||||
// В будущем здесь будет интеграция с Firebase Cloud Messaging
|
||||
// или другим сервисом пуш-уведомлений:
|
||||
/*
|
||||
const message = {
|
||||
notification: {
|
||||
title,
|
||||
body
|
||||
},
|
||||
data: data ? JSON.stringify(data) : undefined,
|
||||
token: await this.getUserPushToken(userId)
|
||||
};
|
||||
|
||||
await admin.messaging().send(message);
|
||||
console.log(`✅ Push notification sent to user ${userId}`);
|
||||
*/
|
||||
|
||||
} catch (error) {
|
||||
console.error(`❌ Error preparing push notification for user ${userId}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
// Получить настройки уведомлений пользователя
|
||||
@@ -300,7 +350,7 @@ export class NotificationService {
|
||||
// Получаем запланированные уведомления
|
||||
const result = await query(`
|
||||
SELECT * FROM scheduled_notifications
|
||||
WHERE scheduled_at <= $1 AND sent = false
|
||||
WHERE scheduled_at <= $1 AND processed = false
|
||||
ORDER BY scheduled_at ASC
|
||||
LIMIT 100
|
||||
`, [new Date()]);
|
||||
@@ -318,10 +368,10 @@ export class NotificationService {
|
||||
// Добавить другие типы уведомлений
|
||||
}
|
||||
|
||||
// Отмечаем как отправленное
|
||||
// Отмечаем как обработанное
|
||||
await query(
|
||||
'UPDATE scheduled_notifications SET sent = true, sent_at = $1 WHERE id = $2',
|
||||
[new Date(), notification.id]
|
||||
'UPDATE scheduled_notifications SET processed = true WHERE id = $1',
|
||||
[notification.id]
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(`Error processing notification ${notification.id}:`, error);
|
||||
|
||||
@@ -95,7 +95,18 @@ export class ProfileService {
|
||||
}
|
||||
|
||||
return this.mapEntityToProfile(result.rows[0]);
|
||||
} // Получение UUID пользователя по Telegram ID
|
||||
}
|
||||
|
||||
// Получение Telegram ID по UUID пользователя
|
||||
async getTelegramIdByUserId(userId: string): Promise<string | null> {
|
||||
const result = await query(`
|
||||
SELECT telegram_id FROM users WHERE id = $1
|
||||
`, [userId]);
|
||||
|
||||
return result.rows.length > 0 ? result.rows[0].telegram_id.toString() : null;
|
||||
}
|
||||
|
||||
// Получение UUID пользователя по Telegram ID
|
||||
async getUserIdByTelegramId(telegramId: string): Promise<string | null> {
|
||||
const result = await query(`
|
||||
SELECT id FROM users WHERE telegram_id = $1
|
||||
@@ -162,7 +173,8 @@ export class ProfileService {
|
||||
case 'photos':
|
||||
case 'interests':
|
||||
updateFields.push(`${this.camelToSnake(key)} = $${paramIndex++}`);
|
||||
updateValues.push(JSON.stringify(value));
|
||||
// Для PostgreSQL массивы передаем как есть, не как JSON строки
|
||||
updateValues.push(value);
|
||||
break;
|
||||
case 'location':
|
||||
if (value && typeof value === 'object' && 'latitude' in value) {
|
||||
@@ -336,7 +348,7 @@ export class ProfileService {
|
||||
return {
|
||||
totalLikes: parseInt(likesResult.rows[0].count),
|
||||
totalMatches: parseInt(matchesResult.rows[0].count),
|
||||
profileViews: 0, // TODO: implement profile views tracking
|
||||
profileViews: await this.getProfileViewsCount(userId),
|
||||
likesReceived: parseInt(likesReceivedResult.rows[0].count)
|
||||
};
|
||||
}
|
||||
@@ -499,4 +511,61 @@ export class ProfileService {
|
||||
profile.isVisible = newVisibility;
|
||||
return profile;
|
||||
}
|
||||
|
||||
// Записать просмотр профиля
|
||||
async recordProfileView(viewerId: string, viewedProfileId: string, viewType: string = 'browse'): Promise<void> {
|
||||
try {
|
||||
await query(`
|
||||
INSERT INTO profile_views (viewer_id, viewed_profile_id, view_type)
|
||||
VALUES (
|
||||
(SELECT id FROM users WHERE telegram_id = $1),
|
||||
(SELECT id FROM profiles WHERE user_id = (SELECT id FROM users WHERE telegram_id = $2)),
|
||||
$3
|
||||
)
|
||||
ON CONFLICT (viewer_id, viewed_profile_id) DO UPDATE
|
||||
SET viewed_at = CURRENT_TIMESTAMP, view_type = EXCLUDED.view_type
|
||||
`, [viewerId, viewedProfileId, viewType]);
|
||||
} catch (error) {
|
||||
console.error('Error recording profile view:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Получить количество просмотров профиля
|
||||
async getProfileViewsCount(userId: string): Promise<number> {
|
||||
try {
|
||||
const result = await query(`
|
||||
SELECT COUNT(*) as count
|
||||
FROM profile_views pv
|
||||
JOIN profiles p ON pv.viewed_profile_id = p.id
|
||||
WHERE p.user_id = $1
|
||||
`, [userId]);
|
||||
|
||||
return parseInt(result.rows[0].count) || 0;
|
||||
} catch (error) {
|
||||
console.error('Error getting profile views count:', error);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Получить список кто просматривал профиль
|
||||
async getProfileViewers(userId: string, limit: number = 10): Promise<Profile[]> {
|
||||
try {
|
||||
const result = await query(`
|
||||
SELECT DISTINCT p.*, u.telegram_id, u.username, u.first_name, u.last_name
|
||||
FROM profile_views pv
|
||||
JOIN profiles target_p ON pv.viewed_profile_id = target_p.id
|
||||
JOIN users viewer_u ON pv.viewer_id = viewer_u.id
|
||||
JOIN profiles p ON viewer_u.id = p.user_id
|
||||
JOIN users u ON p.user_id = u.id
|
||||
WHERE target_p.user_id = $1
|
||||
ORDER BY pv.viewed_at DESC
|
||||
LIMIT $2
|
||||
`, [userId, limit]);
|
||||
|
||||
return result.rows.map((row: any) => this.mapEntityToProfile(row));
|
||||
} catch (error) {
|
||||
console.error('Error getting profile viewers:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user