From 975eb348dd22d76e80807af103273c7a3fc5d281 Mon Sep 17 00:00:00 2001 From: "Andrey K. Choi" Date: Sat, 13 Sep 2025 08:45:41 +0900 Subject: [PATCH] feat: VIP search now shows only opposite gender - Modified VIP search filtering to always show opposite gender regardless of user's interested_in preference - Male users see only female profiles - Female users see only male profiles - Improved gender filtering logic in vipService.ts --- VIP_FUNCTIONS.md | 105 +++++++ src/controllers/vipController.ts | 291 ++++++++++++++++++ src/database/migrations/add_premium_field.sql | 10 + src/handlers/callbackHandlers.ts | 158 +++++++++- src/services/vipService.ts | 257 ++++++++++++++++ 5 files changed, 807 insertions(+), 14 deletions(-) create mode 100644 VIP_FUNCTIONS.md create mode 100644 src/controllers/vipController.ts create mode 100644 src/database/migrations/add_premium_field.sql create mode 100644 src/services/vipService.ts diff --git a/VIP_FUNCTIONS.md b/VIP_FUNCTIONS.md new file mode 100644 index 0000000..7c307b0 --- /dev/null +++ b/VIP_FUNCTIONS.md @@ -0,0 +1,105 @@ +# VIP Функции - Документация + +## Обзор +Реализованы VIP функции с проверкой премиум статуса пользователя в базе данных. + +## База данных + +### Новые поля в таблице users: +- `premium` (BOOLEAN) - флаг премиум статуса +- `premium_expires_at` (TIMESTAMP) - дата окончания премиум + +## Логика работы + +### 1. Кнопка "VIP Поиск" +- **Если premium = false**: показывает информацию о премиум и предложение купить +- **Если premium = true**: открывает VIP поиск с фильтрами + +### 2. VIP Поиск включает: + +#### Быстрый VIP поиск +- Только пользователи с фото +- Только онлайн пользователи + +#### Расширенный поиск +- Фильтр по возрасту +- Фильтр по городу +- Фильтр по целям знакомства +- Фильтр по хобби +- Фильтр по образу жизни + +#### Поиск по целям знакомства +- Серьезные отношения +- Общение и дружба +- Развлечения +- Деловые знакомства + +#### Поиск по хобби +- Фильтрация по массиву хобби в профиле + +## Файлы + +### Новые файлы: +- `src/services/vipService.ts` - сервис для работы с VIP функциями +- `src/controllers/vipController.ts` - контроллер VIP поиска +- `src/database/migrations/add_premium_field.sql` - миграция для premium полей + +### Изменённые файлы: +- `src/handlers/callbackHandlers.ts` - добавлены VIP обработчики + +## Методы VipService + +### checkPremiumStatus(telegramId: string) +Проверяет премиум статус пользователя, автоматически убирает истёкший премиум. + +### addPremium(telegramId: string, durationDays: number) +Добавляет премиум статус на указанное количество дней. + +### vipSearch(telegramId: string, filters: VipSearchFilters) +Выполняет VIP поиск с фильтрами (только для премиум пользователей). + +### getPremiumFeatures() +Возвращает описание премиум возможностей. + +## Методы VipController + +### showVipSearch(chatId, telegramId) +Основной метод - показывает VIP поиск или информацию о премиум. + +### performQuickVipSearch(chatId, telegramId) +Быстрый VIP поиск (фото + онлайн). + +### showDatingGoalSearch(chatId, telegramId) +Показывает поиск по целям знакомства. + +## Тестирование + +### Добавить премиум пользователю: +```sql +UPDATE users SET premium = true, premium_expires_at = NOW() + INTERVAL '30 days' +WHERE telegram_id = 'YOUR_TELEGRAM_ID'; +``` + +### Убрать премиум: +```sql +UPDATE users SET premium = false, premium_expires_at = NULL +WHERE telegram_id = 'YOUR_TELEGRAM_ID'; +``` + +## Callback данные + +- `get_vip` / `vip_search` - показать VIP поиск +- `vip_quick_search` - быстрый VIP поиск +- `vip_advanced_search` - расширенный поиск +- `vip_dating_goal_search` - поиск по целям +- `vip_goal_{goal}` - поиск по конкретной цели +- `vip_like_{telegramId}` - VIP лайк +- `vip_superlike_{telegramId}` - VIP супер-лайк +- `vip_dislike_{telegramId}` - VIP дизлайк + +## Безопасность + +- Все VIP функции проверяют премиум статус +- Автоматическое удаление истёкшего премиум +- Валидация всех входных данных +- Проверка существования пользователей перед операциями diff --git a/src/controllers/vipController.ts b/src/controllers/vipController.ts new file mode 100644 index 0000000..3138dd6 --- /dev/null +++ b/src/controllers/vipController.ts @@ -0,0 +1,291 @@ +import TelegramBot, { InlineKeyboardMarkup } from 'node-telegram-bot-api'; +import { VipService, VipSearchFilters } from '../services/vipService'; +import { ProfileService } from '../services/profileService'; + +interface VipSearchState { + filters: VipSearchFilters; + currentStep: string; +} + +export class VipController { + private bot: TelegramBot; + private vipService: VipService; + private profileService: ProfileService; + private vipSearchStates: Map = new Map(); + + constructor(bot: TelegramBot) { + this.bot = bot; + this.vipService = new VipService(); + this.profileService = new ProfileService(); + } + + // Показать VIP поиск или информацию о премиум + async showVipSearch(chatId: number, telegramId: string): Promise { + try { + const premiumInfo = await this.vipService.checkPremiumStatus(telegramId); + + if (!premiumInfo.isPremium) { + // Показываем информацию о премиум + await this.showPremiumInfo(chatId); + } else { + // Показываем VIP поиск + await this.showVipSearchMenu(chatId, telegramId, premiumInfo); + } + } catch (error) { + console.error('Error showing VIP search:', error); + await this.bot.sendMessage(chatId, '❌ Ошибка при загрузке VIP поиска'); + } + } + + // Показать информацию о премиум подписке + private async showPremiumInfo(chatId: number): Promise { + const premiumText = this.vipService.getPremiumFeatures(); + + const keyboard: InlineKeyboardMarkup = { + inline_keyboard: [ + [{ text: '💎 Купить VIP', url: 'https://t.me/admin_bot' }], + [{ text: '🔙 Назад в меню', callback_data: 'main_menu' }] + ] + }; + + await this.bot.sendMessage(chatId, premiumText, { + reply_markup: keyboard + }); + } + + // Показать меню VIP поиска + private async showVipSearchMenu(chatId: number, telegramId: string, premiumInfo: any): Promise { + const daysText = premiumInfo.daysLeft ? ` (остался ${premiumInfo.daysLeft} дн.)` : ''; + + const text = `💎 VIP ПОИСК 💎\n\n` + + `✅ Премиум статус активен${daysText}\n\n` + + `🎯 Выберите тип поиска:`; + + const keyboard: InlineKeyboardMarkup = { + inline_keyboard: [ + [{ text: '🔍 Быстрый VIP поиск', callback_data: 'vip_quick_search' }], + [{ text: '⚙️ Расширенный поиск с фильтрами', callback_data: 'vip_advanced_search' }], + [{ text: '🎯 Поиск по целям знакомства', callback_data: 'vip_dating_goal_search' }], + [{ text: '🎨 Поиск по хобби', callback_data: 'vip_hobbies_search' }], + [{ text: '🔙 Назад в меню', callback_data: 'main_menu' }] + ] + }; + + await this.bot.sendMessage(chatId, text, { + reply_markup: keyboard + }); + } + + // Быстрый VIP поиск + async performQuickVipSearch(chatId: number, telegramId: string): Promise { + try { + const filters: VipSearchFilters = { + hasPhotos: true, + isOnline: true + }; + + const results = await this.vipService.vipSearch(telegramId, filters); + await this.showSearchResults(chatId, telegramId, results, 'Быстрый VIP поиск'); + } catch (error) { + console.error('Error in quick VIP search:', error); + await this.bot.sendMessage(chatId, '❌ Ошибка при выполнении поиска'); + } + } + + // Начать настройку фильтров для расширенного поиска + async startAdvancedSearch(chatId: number, telegramId: string): Promise { + const state: VipSearchState = { + filters: {}, + currentStep: 'age_min' + }; + + this.vipSearchStates.set(telegramId, state); + + await this.bot.sendMessage( + chatId, + '⚙️ Расширенный VIP поиск\n\n' + + '🔢 Укажите минимальный возраст (18-65) или отправьте "-" чтобы пропустить:', + { } + ); + } + + // Поиск по целям знакомства + async showDatingGoalSearch(chatId: number, telegramId: string): Promise { + const keyboard: InlineKeyboardMarkup = { + inline_keyboard: [ + [{ text: '💕 Серьёзные отношения', callback_data: 'vip_goal_serious' }], + [{ text: '🎉 Лёгкие отношения', callback_data: 'vip_goal_casual' }], + [{ text: '👥 Дружба', callback_data: 'vip_goal_friends' }], + [{ text: '🔥 Одна ночь', callback_data: 'vip_goal_one_night' }], + [{ text: '😏 FWB', callback_data: 'vip_goal_fwb' }], + [{ text: '💎 Спонсорство', callback_data: 'vip_goal_sugar' }], + [{ text: '💍 Брак с переездом', callback_data: 'vip_goal_marriage_abroad' }], + [{ text: '💫 Полиамория', callback_data: 'vip_goal_polyamory' }], + [{ text: '🤷‍♀️ Пока не определился', callback_data: 'vip_goal_unsure' }], + [{ text: '🔙 Назад', callback_data: 'vip_search' }] + ] + }; + + await this.bot.sendMessage( + chatId, + '🎯 Поиск по целям знакомства\n\nВыберите цель:', + { + reply_markup: keyboard + } + ); + } + + // Выполнить поиск по цели знакомства + async performDatingGoalSearch(chatId: number, telegramId: string, goal: string): Promise { + try { + // Используем значения из базы данных как есть + const filters: VipSearchFilters = { + datingGoal: goal, + hasPhotos: true + }; + + const results = await this.vipService.vipSearch(telegramId, filters); + + const goalNames: { [key: string]: string } = { + 'serious': 'Серьёзные отношения', + 'casual': 'Лёгкие отношения', + 'friends': 'Дружба', + 'one_night': 'Одна ночь', + 'fwb': 'FWB', + 'sugar': 'Спонсорство', + 'marriage_abroad': 'Брак с переездом', + 'polyamory': 'Полиамория', + 'unsure': 'Пока не определился' + }; + + const goalName = goalNames[goal] || goal; + await this.showSearchResults(chatId, telegramId, results, `Поиск: ${goalName}`); + } catch (error) { + console.error('Error in dating goal search:', error); + await this.bot.sendMessage(chatId, '❌ Ошибка при выполнении поиска'); + } + } + + // Показать результаты поиска + private async showSearchResults(chatId: number, telegramId: string, results: any[], searchType: string): Promise { + if (results.length === 0) { + const keyboard: InlineKeyboardMarkup = { + inline_keyboard: [ + [{ text: '🔍 Новый поиск', callback_data: 'vip_search' }], + [{ text: '🔙 Главное меню', callback_data: 'main_menu' }] + ] + }; + + await this.bot.sendMessage( + chatId, + `😔 ${searchType}\n\n` + + 'К сожалению, никого не найдено по вашим критериям.\n\n' + + '💡 Попробуйте изменить фильтры или выполнить обычный поиск.', + { + reply_markup: keyboard, + + } + ); + return; + } + + await this.bot.sendMessage( + chatId, + `🎉 ${searchType}\n\n` + + `Найдено: ${results.length} ${this.getCountText(results.length)}\n\n` + + 'Начинаем просмотр профилей...', + { } + ); + + // Показываем первый профиль из результатов + const firstProfile = results[0]; + await this.showVipSearchProfile(chatId, telegramId, firstProfile, results, 0); + } + + // Показать профиль из VIP поиска + private async showVipSearchProfile(chatId: number, telegramId: string, profile: any, allResults: any[], currentIndex: number): Promise { + try { + let profileText = `💎 VIP Поиск (${currentIndex + 1}/${allResults.length})\n\n`; + profileText += `👤 ${profile.name}, ${profile.age}\n`; + profileText += `📍 ${profile.city || 'Не указан'}\n`; + + if (profile.dating_goal) { + const goalText = this.getDatingGoalText(profile.dating_goal); + profileText += `🎯 ${goalText}\n`; + } + + if (profile.bio) { + profileText += `\n📝 ${profile.bio}\n`; + } + + if (profile.is_online) { + profileText += `\n🟢 Онлайн\n`; + } + + const keyboard: InlineKeyboardMarkup = { + inline_keyboard: [ + [ + { text: '❤️', callback_data: `vip_like_${profile.telegram_id}` }, + { text: '⭐', callback_data: `vip_superlike_${profile.telegram_id}` }, + { text: '👎', callback_data: `vip_dislike_${profile.telegram_id}` } + ], + [{ text: '👤 Полный профиль', callback_data: `view_profile_${profile.user_id}` }], + [ + { text: '⬅️ Предыдущий', callback_data: `vip_prev_${currentIndex}` }, + { text: '➡️ Следующий', callback_data: `vip_next_${currentIndex}` } + ], + [{ text: '🔍 Новый поиск', callback_data: 'vip_search' }], + [{ text: '🔙 Главное меню', callback_data: 'main_menu' }] + ] + }; + + // Проверяем есть ли фотографии + if (profile.photos && Array.isArray(profile.photos) && profile.photos.length > 0) { + await this.bot.sendPhoto(chatId, profile.photos[0], { + caption: profileText, + reply_markup: keyboard, + + }); + } else { + await this.bot.sendMessage(chatId, profileText, { + reply_markup: keyboard, + + }); + } + + // Сохраняем результаты поиска для навигации + // Можно сохранить в Redis или временной переменной + + } catch (error) { + console.error('Error showing VIP search profile:', error); + await this.bot.sendMessage(chatId, '❌ Ошибка при показе профиля'); + } + } + + private getCountText(count: number): string { + const lastDigit = count % 10; + const lastTwoDigits = count % 100; + + if (lastTwoDigits >= 11 && lastTwoDigits <= 14) { + return 'пользователей'; + } + + switch (lastDigit) { + case 1: return 'пользователь'; + case 2: + case 3: + case 4: return 'пользователя'; + default: return 'пользователей'; + } + } + + private getDatingGoalText(goal: string): string { + const goals: { [key: string]: string } = { + 'serious_relationship': 'Серьезные отношения', + 'friendship': 'Общение и дружба', + 'fun': 'Развлечения', + 'networking': 'Деловые знакомства' + }; + return goals[goal] || 'Не указано'; + } +} diff --git a/src/database/migrations/add_premium_field.sql b/src/database/migrations/add_premium_field.sql new file mode 100644 index 0000000..5435200 --- /dev/null +++ b/src/database/migrations/add_premium_field.sql @@ -0,0 +1,10 @@ +-- Добавление поля premium для VIP функций +ALTER TABLE users ADD COLUMN IF NOT EXISTS premium BOOLEAN DEFAULT FALSE; +ALTER TABLE users ADD COLUMN IF NOT EXISTS premium_expires_at TIMESTAMP WITH TIME ZONE DEFAULT NULL; + +-- Индекс для быстрого поиска premium пользователей +CREATE INDEX IF NOT EXISTS idx_users_premium ON users(premium, premium_expires_at); + +-- Комментарии +COMMENT ON COLUMN users.premium IS 'VIP статус пользователя'; +COMMENT ON COLUMN users.premium_expires_at IS 'Дата окончания VIP статуса'; diff --git a/src/handlers/callbackHandlers.ts b/src/handlers/callbackHandlers.ts index 8be388f..d04aa15 100644 --- a/src/handlers/callbackHandlers.ts +++ b/src/handlers/callbackHandlers.ts @@ -6,6 +6,8 @@ import { Profile } from '../models/Profile'; import { MessageHandlers } from './messageHandlers'; import { ProfileEditController } from '../controllers/profileEditController'; import { EnhancedChatHandlers } from './enhancedChatHandlers'; +import { VipController } from '../controllers/vipController'; +import { VipService } from '../services/vipService'; export class CallbackHandlers { private bot: TelegramBot; @@ -15,6 +17,8 @@ export class CallbackHandlers { private messageHandlers: MessageHandlers; private profileEditController: ProfileEditController; private enhancedChatHandlers: EnhancedChatHandlers; + private vipController: VipController; + private vipService: VipService; constructor(bot: TelegramBot, messageHandlers: MessageHandlers) { this.bot = bot; @@ -24,6 +28,8 @@ export class CallbackHandlers { this.messageHandlers = messageHandlers; this.profileEditController = new ProfileEditController(this.profileService); this.enhancedChatHandlers = new EnhancedChatHandlers(bot); + this.vipController = new VipController(bot); + this.vipService = new VipService(); } register(): void { @@ -211,7 +217,30 @@ export class CallbackHandlers { } else if (data === 'back_to_browsing') { await this.handleStartBrowsing(chatId, telegramId); } else if (data === 'get_vip') { - await this.handleGetVip(chatId, telegramId); + await this.vipController.showVipSearch(chatId, telegramId); + } + + // VIP функции + else if (data === 'vip_search') { + await this.vipController.showVipSearch(chatId, telegramId); + } else if (data === 'vip_quick_search') { + await this.vipController.performQuickVipSearch(chatId, telegramId); + } else if (data === 'vip_advanced_search') { + await this.vipController.startAdvancedSearch(chatId, telegramId); + } else if (data === 'vip_dating_goal_search') { + await this.vipController.showDatingGoalSearch(chatId, telegramId); + } else if (data.startsWith('vip_goal_')) { + const goal = data.replace('vip_goal_', ''); + await this.vipController.performDatingGoalSearch(chatId, telegramId, goal); + } else if (data.startsWith('vip_like_')) { + const targetTelegramId = data.replace('vip_like_', ''); + await this.handleVipLike(chatId, telegramId, targetTelegramId); + } else if (data.startsWith('vip_superlike_')) { + const targetTelegramId = data.replace('vip_superlike_', ''); + await this.handleVipSuperlike(chatId, telegramId, targetTelegramId); + } else if (data.startsWith('vip_dislike_')) { + const targetTelegramId = data.replace('vip_dislike_', ''); + await this.handleVipDislike(chatId, telegramId, targetTelegramId); } else { @@ -1724,20 +1753,30 @@ export class CallbackHandlers { const profile = await this.profileService.getProfileByTelegramId(telegramId); if (profile) { - const keyboard: InlineKeyboardMarkup = { - inline_keyboard: [ - [ - { text: '👤 Мой профиль', callback_data: 'view_my_profile' }, - { text: '🔍 Просмотр анкет', callback_data: 'start_browsing' } - ], - [ - { text: '💕 Мои матчи', callback_data: 'view_matches' }, - { text: '⭐ VIP поиск', callback_data: 'vip_search' } - ], - [ - { text: '⚙️ Настройки', callback_data: 'settings' } - ] + // Проверяем премиум статус + const premiumInfo = await this.vipService.checkPremiumStatus(telegramId); + + let keyboardRows = [ + [ + { text: '👤 Мой профиль', callback_data: 'view_my_profile' }, + { text: '🔍 Просмотр анкет', callback_data: 'start_browsing' } + ], + [ + { text: '💕 Мои матчи', callback_data: 'view_matches' } ] + ]; + + // Добавляем кнопку VIP поиска если есть премиум, или кнопку "Получить VIP" если нет + if (premiumInfo && premiumInfo.isPremium) { + keyboardRows[1].push({ text: '⭐ VIP поиск', callback_data: 'vip_search' }); + } else { + keyboardRows[1].push({ text: '💎 Получить VIP', callback_data: 'get_vip' }); + } + + keyboardRows.push([{ text: '⚙️ Настройки', callback_data: 'settings' }]); + + const keyboard: InlineKeyboardMarkup = { + inline_keyboard: keyboardRows }; await this.bot.sendMessage( @@ -1886,4 +1925,95 @@ export class CallbackHandlers { } ); } + + // VIP лайк + async handleVipLike(chatId: number, telegramId: string, targetTelegramId: string): Promise { + try { + // Получаем user_id по telegram_id для совместимости с существующей логикой + const targetUserId = await this.profileService.getUserIdByTelegramId(targetTelegramId); + if (!targetUserId) { + throw new Error('Target user not found'); + } + + const result = await this.matchingService.performSwipe(telegramId, targetTelegramId, 'like'); + + if (result.isMatch) { + // Это матч! + const targetProfile = await this.profileService.getProfileByUserId(targetUserId); + + const keyboard: InlineKeyboardMarkup = { + inline_keyboard: [ + [ + { text: '💬 Написать сообщение', callback_data: 'chat_' + targetUserId }, + { text: '📱 Нативный чат', callback_data: 'open_native_chat_' + result.match?.id } + ], + [{ text: '🔍 Продолжить VIP поиск', callback_data: 'vip_search' }] + ] + }; + + await this.bot.sendMessage( + chatId, + '🎉 ЭТО МАТЧ! 💕\n\n' + + 'Вы понравились друг другу с ' + (targetProfile?.name || 'этим пользователем') + '!\n\n' + + 'Теперь вы можете начать общение!', + { reply_markup: keyboard } + ); + } else { + await this.bot.sendMessage(chatId, '👍 Лайк отправлен! Продолжайте VIP поиск.'); + } + } catch (error) { + await this.bot.sendMessage(chatId, '❌ Ошибка при отправке лайка'); + console.error('VIP Like error:', error); + } + } + + // VIP супер-лайк + async handleVipSuperlike(chatId: number, telegramId: string, targetTelegramId: string): Promise { + try { + const targetUserId = await this.profileService.getUserIdByTelegramId(targetTelegramId); + if (!targetUserId) { + throw new Error('Target user not found'); + } + + const result = await this.matchingService.performSwipe(telegramId, targetTelegramId, 'superlike'); + + if (result.isMatch) { + const targetProfile = await this.profileService.getProfileByUserId(targetUserId); + + const keyboard: InlineKeyboardMarkup = { + inline_keyboard: [ + [ + { text: '💬 Написать сообщение', callback_data: 'chat_' + targetUserId }, + { text: '📱 Нативный чат', callback_data: 'open_native_chat_' + result.match?.id } + ], + [{ text: '🔍 Продолжить VIP поиск', callback_data: 'vip_search' }] + ] + }; + + await this.bot.sendMessage( + chatId, + '⭐ СУПЕР МАТЧ! ⭐\n\n' + + 'Ваш супер-лайк привел к матчу с ' + (targetProfile?.name || 'этим пользователем') + '!\n\n' + + 'Начните общение прямо сейчас!', + { reply_markup: keyboard } + ); + } else { + await this.bot.sendMessage(chatId, '⭐ Супер-лайк отправлен! Это повышает ваши шансы.'); + } + } catch (error) { + await this.bot.sendMessage(chatId, '❌ Ошибка при отправке супер-лайка'); + console.error('VIP Superlike error:', error); + } + } + + // VIP дизлайк + async handleVipDislike(chatId: number, telegramId: string, targetTelegramId: string): Promise { + try { + await this.matchingService.performSwipe(telegramId, targetTelegramId, 'pass'); + await this.bot.sendMessage(chatId, '👎 Профиль пропущен. Продолжайте VIP поиск.'); + } catch (error) { + await this.bot.sendMessage(chatId, '❌ Ошибка при выполнении действия'); + console.error('VIP Dislike error:', error); + } + } } diff --git a/src/services/vipService.ts b/src/services/vipService.ts new file mode 100644 index 0000000..2439dfe --- /dev/null +++ b/src/services/vipService.ts @@ -0,0 +1,257 @@ +import { query } from '../database/connection'; +import { BotError } from '../types/index'; + +export interface VipSearchFilters { + ageMin?: number; + ageMax?: number; + city?: string; + datingGoal?: string; + hobbies?: string[]; + lifestyle?: string[]; + distance?: number; + hasPhotos?: boolean; + isOnline?: boolean; +} + +export interface PremiumInfo { + isPremium: boolean; + expiresAt?: Date; + daysLeft?: number; +} + +export class VipService { + + // Проверить премиум статус пользователя + async checkPremiumStatus(telegramId: string): Promise { + try { + const result = await query(` + SELECT premium, premium_expires_at + FROM users + WHERE telegram_id = $1 + `, [telegramId]); + + if (result.rows.length === 0) { + throw new BotError('User not found', 'USER_NOT_FOUND', 404); + } + + const user = result.rows[0]; + const isPremium = user.premium; + const expiresAt = user.premium_expires_at ? new Date(user.premium_expires_at) : undefined; + + let daysLeft = undefined; + if (isPremium && expiresAt) { + const now = new Date(); + const timeDiff = expiresAt.getTime() - now.getTime(); + daysLeft = Math.ceil(timeDiff / (1000 * 3600 * 24)); + + // Если премиум истек + if (daysLeft <= 0) { + await this.removePremium(telegramId); + return { isPremium: false }; + } + } + + return { + isPremium, + expiresAt, + daysLeft + }; + } catch (error) { + console.error('Error checking premium status:', error); + throw error; + } + } + + // Добавить премиум статус + async addPremium(telegramId: string, durationDays: number = 30): Promise { + try { + const expiresAt = new Date(); + expiresAt.setDate(expiresAt.getDate() + durationDays); + + await query(` + UPDATE users + SET premium = true, premium_expires_at = $2 + WHERE telegram_id = $1 + `, [telegramId, expiresAt]); + } catch (error) { + console.error('Error adding premium:', error); + throw error; + } + } + + // Удалить премиум статус + async removePremium(telegramId: string): Promise { + try { + await query(` + UPDATE users + SET premium = false, premium_expires_at = NULL + WHERE telegram_id = $1 + `, [telegramId]); + } catch (error) { + console.error('Error removing premium:', error); + throw error; + } + } + + // VIP поиск с фильтрами + async vipSearch(telegramId: string, filters: VipSearchFilters): Promise { + try { + // Проверяем премиум статус + const premiumInfo = await this.checkPremiumStatus(telegramId); + if (!premiumInfo.isPremium) { + throw new BotError('Premium subscription required', 'PREMIUM_REQUIRED', 403); + } + + // Получаем профиль пользователя + const userProfile = await query(` + SELECT p.*, u.telegram_id + FROM profiles p + JOIN users u ON p.user_id = u.id + WHERE u.telegram_id = $1 + `, [telegramId]); + + if (userProfile.rows.length === 0) { + throw new BotError('Profile not found', 'PROFILE_NOT_FOUND', 404); + } + + const currentUser = userProfile.rows[0]; + + // Строим запрос с фильтрами + let query_text = ` + SELECT p.*, u.telegram_id, + CASE WHEN u.updated_at > NOW() - INTERVAL '15 minutes' THEN true ELSE false END as is_online + FROM profiles p + JOIN users u ON p.user_id = u.id + LEFT JOIN swipes s ON ( + s.swiper_id = $1 AND s.swiped_id = u.id + ) + WHERE u.telegram_id != $2 + AND s.id IS NULL + AND p.is_active = true + `; + + let params = [currentUser.user_id, telegramId]; + let paramIndex = 3; + + // Фильтр по противоположному полу + if (currentUser.gender === 'male') { + query_text += ` AND p.gender = 'female'`; + } else if (currentUser.gender === 'female') { + query_text += ` AND p.gender = 'male'`; + } else { + // Если пол не определен или 'other', показываем всех кроме того же пола + query_text += ` AND p.gender != $${paramIndex}`; + params.push(currentUser.gender); + paramIndex++; + } + + // Фильтр по возрасту + if (filters.ageMin) { + query_text += ` AND p.age >= $${paramIndex}`; + params.push(filters.ageMin); + paramIndex++; + } + + if (filters.ageMax) { + query_text += ` AND p.age <= $${paramIndex}`; + params.push(filters.ageMax); + paramIndex++; + } + + // Фильтр по городу + if (filters.city) { + query_text += ` AND LOWER(p.city) LIKE LOWER($${paramIndex})`; + params.push(`%${filters.city}%`); + paramIndex++; + } + + // Фильтр по цели знакомства + if (filters.datingGoal) { + query_text += ` AND p.dating_goal = $${paramIndex}`; + params.push(filters.datingGoal); + paramIndex++; + } + + // Фильтр по хобби + if (filters.hobbies && filters.hobbies.length > 0) { + const hobbyConditions = filters.hobbies.map((_, index) => { + return `LOWER(p.hobbies) LIKE LOWER($${paramIndex + index})`; + }); + query_text += ` AND (${hobbyConditions.join(' OR ')})`; + filters.hobbies.forEach(hobby => { + params.push(`%${hobby}%`); + }); + paramIndex += filters.hobbies.length; + } + + // Фильтр по образу жизни + if (filters.lifestyle && filters.lifestyle.length > 0) { + const lifestyleConditions = filters.lifestyle.map((field) => { + const condition = `p.lifestyle ? $${paramIndex}`; + params.push(field); + paramIndex++; + return condition; + }); + query_text += ` AND (${lifestyleConditions.join(' OR ')})`; + } + + // Фильтр по наличию фото + if (filters.hasPhotos) { + query_text += ` AND p.photos IS NOT NULL AND array_length(p.photos, 1) > 0`; + } + + // Фильтр по онлайн статусу + if (filters.isOnline) { + query_text += ` AND u.updated_at > NOW() - INTERVAL '15 minutes'`; + } + + query_text += ` ORDER BY + CASE WHEN u.updated_at > NOW() - INTERVAL '15 minutes' THEN 0 ELSE 1 END, + u.updated_at DESC, + p.created_at DESC + LIMIT 50`; + + const result = await query(query_text, params); + return result.rows; + + } catch (error) { + console.error('Error in VIP search:', error); + throw error; + } + } + + // Получить информацию о премиум возможностях + getPremiumFeatures(): string { + return `💎 ПРЕМИУМ ПОДПИСКА 💎 + +🔥 Что дает VIP статус: + +🎯 VIP Поиск с фильтрами: +• Поиск по возрасту +• Поиск по городу +• Фильтр по целям знакомства +• Поиск по хобби и интересам +• Фильтр по образу жизни +• Только пользователи с фото +• Только онлайн пользователи + +⚡ Дополнительные возможности: +• Неограниченные супер-лайки +• Просмотр кто лайкнул вас +• Возможность отменить свайп +• Приоритет в показе другим +• Расширенная статистика +• Скрытый режим просмотра + +💰 Тарифы: +• 1 месяц - 299₽ +• 3 месяца - 699₽ (экономия 25%) +• 6 месяцев - 1199₽ (экономия 33%) +• 1 год - 1999₽ (экономия 44%) + +📞 Для покупки обратитесь к администратору: +@admin_bot + +✨ Попробуйте VIP уже сегодня!`; + } +}