diff --git a/.gitignore b/.gitignore index b512c09..5baa59d 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -node_modules \ No newline at end of file +db \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 22e420b..ecb18bf 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,8 +4,8 @@ WORKDIR /app COPY package*.json /app/ COPY src/ /app/src/ -COPY db/shop.db /app/shop.db +#COPY db/shop.db /app/shop.db RUN npm install -CMD ["node", "src/index.js"] \ No newline at end of file +CMD ["node", "src/index.js"] diff --git a/docker-compose.yml b/docker-compose.yml index 43e8e52..f915c89 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,7 +10,7 @@ services: restart: always environment: - BOT_TOKEN=7626758249:AAEdcbXJpW1VsnJJtc8kZ5VBsYMFR242wgk - - ADMIN_IDS=732563549,390431690 + - ADMIN_IDS=732563549,390431690,217546867 - SUPPORT_LINK=https://t.me/neroworm - CATALOG_PATH=./catalog volumes: diff --git a/src/context/bot.js b/src/context/bot.js new file mode 100644 index 0000000..5952fcd --- /dev/null +++ b/src/context/bot.js @@ -0,0 +1,17 @@ +import TelegramBot from "node-telegram-bot-api"; +import config from "../config/config.js"; + +const initBot = () => { + try { + const bot = new TelegramBot(config.BOT_TOKEN, {polling: true}); + console.log('Bot initialized successfully'); + return bot; + } catch (error) { + console.error('Failed to initialize bot:', error); + process.exit(1); + } +}; + +const bot = initBot(); + +export default bot; \ No newline at end of file diff --git a/src/context/userStates.js b/src/context/userStates.js new file mode 100644 index 0000000..d341377 --- /dev/null +++ b/src/context/userStates.js @@ -0,0 +1,2 @@ +const userStates = new Map(); +export default userStates; \ No newline at end of file diff --git a/src/handlers/adminDumpHandler.js b/src/handlers/adminHandlers/adminDumpHandler.js similarity index 61% rename from src/handlers/adminDumpHandler.js rename to src/handlers/adminHandlers/adminDumpHandler.js index 34e87b6..095efdb 100644 --- a/src/handlers/adminDumpHandler.js +++ b/src/handlers/adminHandlers/adminDumpHandler.js @@ -1,22 +1,24 @@ -import config from '../config/config.js'; +import config from '../../config/config.js'; import fs from "fs"; -import db from "../config/database.js"; +import db from "../../config/database.js"; import archiver from "archiver"; import decompress from "decompress"; +import bot from "../../context/bot.js"; +import userStates from "../../context/userStates.js"; export default class AdminDumpHandler { - constructor(bot) { - this.bot = bot; - this.userStates = new Map(); - } - - isAdmin(userId) { + static isAdmin(userId) { return config.ADMIN_IDS.includes(userId.toString()); } - async handleDump(msg) { + static async handleDump(msg) { const chatId = msg.chat.id; + if (!this.isAdmin(msg.from.id)) { + await bot.sendMessage(chatId, 'Unauthorized access.'); + return; + } + const keyboard = { inline_keyboard: [ [ @@ -27,10 +29,14 @@ export default class AdminDumpHandler { ] } - await this.bot.sendMessage(chatId, 'Choose an option', {reply_markup: keyboard}); + await bot.sendMessage(chatId, 'Choose an option', {reply_markup: keyboard}); } - async handleExportDatabase(callbackQuery) { + static async handleExportDatabase(callbackQuery) { + if (!this.isAdmin(callbackQuery.from.id)) { + return; + } + const chatId = callbackQuery.message.chat.id; const tables = [ @@ -69,16 +75,20 @@ export default class AdminDumpHandler { await archive.finalize(); output.on('close', () => { - this.bot.sendDocument(chatId, './dump.zip', {caption: 'Database dump'}); + bot.sendDocument(chatId, './dump.zip', {caption: 'Database dump'}); }); } - async handleImportDatabase(callbackQuery) { + static async handleImportDatabase(callbackQuery) { + if (!this.isAdmin(callbackQuery.from.id)) { + return; + } + const chatId = callbackQuery.message.chat.id; - this.userStates.set(chatId, { action: 'upload_database_dump' }); + userStates.set(chatId, { action: 'upload_database_dump' }); - await this.bot.editMessageText( + await bot.editMessageText( 'Please upload database dump', { chat_id: chatId, @@ -87,7 +97,7 @@ export default class AdminDumpHandler { ); } - async getDumpStatistic() { + static async getDumpStatistic() { const tables = [ "categories", "crypto_wallets", @@ -109,36 +119,43 @@ export default class AdminDumpHandler { return stat; } - async handleDumpImport(msg) { + static async handleDumpImport(msg) { const chatId = msg.chat.id; - const state = this.userStates.get(chatId); + + const state = userStates.get(chatId); if (!state || state.action !== 'upload_database_dump') { return false; } + if (!this.isAdmin(msg.from.id)) { + await bot.sendMessage(chatId, 'Unauthorized access.'); + return; + } + if (msg.document) { if (!msg.document.file_name.endsWith('.zip')) { - await this.bot.sendMessage(chatId, 'Please upload a .zip file.'); + await bot.sendMessage(chatId, 'Please upload a .zip file.'); return true; } - const file = await this.bot.getFile(msg.document.file_id); - const fileContent = await this.bot.downloadFile(file.file_id, '.'); + const file = await bot.getFile(msg.document.file_id); + const fileContent = await bot.downloadFile(file.file_id, '.'); await decompress(fileContent, './dump'); const statistics = await this.getDumpStatistic(); - await this.bot.sendMessage(chatId, JSON.stringify(statistics, null, 2)); - this.userStates.delete(chatId); + await bot.sendMessage(chatId, JSON.stringify(statistics, null, 2)); + userStates.delete(chatId); } else { - await this.bot.sendMessage(chatId, 'Please upload a valid .zip file.'); + await bot.sendMessage(chatId, 'Please upload a valid .zip file.'); return true; } } - async confirmImport(callbackQuery) { - + static async confirmImport(callbackQuery) { + if (!this.isAdmin(callbackQuery.from.id)) { + return; + } } - } \ No newline at end of file diff --git a/src/handlers/adminHandler.js b/src/handlers/adminHandlers/adminHandler.js similarity index 65% rename from src/handlers/adminHandler.js rename to src/handlers/adminHandlers/adminHandler.js index ef86a95..6a9b5ad 100644 --- a/src/handlers/adminHandler.js +++ b/src/handlers/adminHandlers/adminHandler.js @@ -1,19 +1,16 @@ -import config from '../config/config.js'; +import config from '../../config/config.js'; +import bot from "../../context/bot.js"; export default class AdminHandler { - constructor(bot) { - this.bot = bot; - } - - isAdmin(userId) { + static isAdmin(userId) { return config.ADMIN_IDS.includes(userId.toString()); } - async handleAdminCommand(msg) { + static async handleAdminCommand(msg) { const chatId = msg.chat.id; if (!this.isAdmin(msg.from.id)) { - await this.bot.sendMessage(chatId, 'Unauthorized access.'); + await bot.sendMessage(chatId, 'Unauthorized access.'); return; } @@ -28,6 +25,6 @@ export default class AdminHandler { } }; - await this.bot.sendMessage(chatId, 'Admin Panel:', keyboard); + await bot.sendMessage(chatId, 'Admin Panel:', keyboard); } } \ No newline at end of file diff --git a/src/handlers/adminLocationHandler.js b/src/handlers/adminHandlers/adminLocationHandler.js similarity index 79% rename from src/handlers/adminLocationHandler.js rename to src/handlers/adminHandlers/adminLocationHandler.js index 7c05143..1f4585b 100644 --- a/src/handlers/adminLocationHandler.js +++ b/src/handlers/adminHandlers/adminLocationHandler.js @@ -1,23 +1,24 @@ -import db from '../config/database.js'; -import Validators from '../utils/validators.js'; -import config from '../config/config.js'; +import db from '../../config/database.js'; +import Validators from '../../utils/validators.js'; +import config from '../../config/config.js'; +import userStates from "../../context/userStates.js"; +import bot from "../../context/bot.js"; export default class AdminLocationHandler { - constructor(bot) { - this.bot = bot; - this.userStates = new Map(); - } - - isAdmin(userId) { + static isAdmin(userId) { return config.ADMIN_IDS.includes(userId.toString()); } - async handleAddLocation(callbackQuery) { + static async handleAddLocation(callbackQuery) { + if (!this.isAdmin(callbackQuery.from.id)) { + return; + } + const chatId = callbackQuery.message.chat.id; - this.userStates.set(chatId, { action: 'add_location' }); + userStates.set(chatId, { action: 'add_location' }); - await this.bot.editMessageText( + await bot.editMessageText( 'Please enter the location in the following format:\nCountry|City|District', { chat_id: chatId, @@ -29,15 +30,22 @@ export default class AdminLocationHandler { ); } - async handleLocationInput(msg) { + static async handleLocationInput(msg) { const chatId = msg.chat.id; - const state = this.userStates.get(chatId); + const state = userStates.get(chatId); - if (!state || state.action !== 'add_location') return false; + if (!state || state.action !== 'add_location') { + return false; + } + + if (!this.isAdmin(msg.from.id)) { + await bot.sendMessage(chatId, 'Unauthorized access.'); + return; + } const parts = msg.text.split('|').map(s => s.trim()); if (parts.length !== 3) { - await this.bot.sendMessage( + await bot.sendMessage( chatId, 'Invalid format. Please use: Country|City|District' ); @@ -47,7 +55,7 @@ export default class AdminLocationHandler { const [country, city, district] = parts; if (!Validators.isValidLocation(country, city, district)) { - await this.bot.sendMessage( + await bot.sendMessage( chatId, 'Invalid location data. All fields are required.' ); @@ -65,7 +73,7 @@ export default class AdminLocationHandler { await db.runAsync('COMMIT'); if (result.changes > 0) { - await this.bot.sendMessage( + await bot.sendMessage( chatId, `✅ Location added successfully!\n\nCountry: ${country}\nCity: ${city}\nDistrict: ${district}`, { @@ -81,12 +89,12 @@ export default class AdminLocationHandler { throw new Error('Failed to insert location'); } - this.userStates.delete(chatId); + userStates.delete(chatId); } catch (error) { await db.runAsync('ROLLBACK'); if (error.code === 'SQLITE_CONSTRAINT') { - await this.bot.sendMessage( + await bot.sendMessage( chatId, '❌ This location already exists.', { @@ -99,7 +107,7 @@ export default class AdminLocationHandler { ); } else { console.error('Error adding location:', error); - await this.bot.sendMessage( + await bot.sendMessage( chatId, '❌ Error adding location. Please try again.', { @@ -116,16 +124,16 @@ export default class AdminLocationHandler { return true; } - async handleViewLocations(msg) { + static async handleViewLocations(msg) { const chatId = msg.chat?.id || msg.message?.chat.id; const messageId = msg.message?.message_id; if (!this.isAdmin(msg.from?.id || msg.message?.from.id)) { - await this.bot.sendMessage(chatId, 'Unauthorized access.'); + await bot.sendMessage(chatId, 'Unauthorized access.'); return; } - this.userStates.delete(chatId); + userStates.delete(chatId); try { const locations = await db.allAsync(` @@ -146,13 +154,13 @@ export default class AdminLocationHandler { }; if (messageId) { - await this.bot.editMessageText(message, { + await bot.editMessageText(message, { chat_id: chatId, message_id: messageId, reply_markup: keyboard }); } else { - await this.bot.sendMessage(chatId, message, { reply_markup: keyboard }); + await bot.sendMessage(chatId, message, { reply_markup: keyboard }); } return; } @@ -179,25 +187,29 @@ export default class AdminLocationHandler { }; if (messageId) { - await this.bot.editMessageText(message, { + await bot.editMessageText(message, { chat_id: chatId, message_id: messageId, reply_markup: keyboard, parse_mode: 'Markdown' }); } else { - await this.bot.sendMessage(chatId, message, { + await bot.sendMessage(chatId, message, { reply_markup: keyboard, parse_mode: 'Markdown' }); } } catch (error) { console.error('Error viewing locations:', error); - await this.bot.sendMessage(chatId, 'Error loading locations. Please try again.'); + await bot.sendMessage(chatId, 'Error loading locations. Please try again.'); } } - async handleDeleteLocation(callbackQuery) { + static async handleDeleteLocation(callbackQuery) { + if (!this.isAdmin(callbackQuery.from.id)) { + return; + } + const chatId = callbackQuery.message.chat.id; try { @@ -221,7 +233,7 @@ export default class AdminLocationHandler { keyboard.inline_keyboard.push([{ text: '« Back', callback_data: 'view_locations' }]); - await this.bot.editMessageText( + await bot.editMessageText( '❌ Select location to delete:\n\n*Note:* Deleting a location will also remove all associated products and categories!', { chat_id: chatId, @@ -232,11 +244,15 @@ export default class AdminLocationHandler { ); } catch (error) { console.error('Error in handleDeleteLocation:', error); - await this.bot.sendMessage(chatId, 'Error loading locations. Please try again.'); + await bot.sendMessage(chatId, 'Error loading locations. Please try again.'); } } - async handleConfirmDelete(callbackQuery) { + static async handleConfirmDelete(callbackQuery) { + if (!this.isAdmin(callbackQuery.from.id)) { + return; + } + const chatId = callbackQuery.message.chat.id; const [country, city, district] = callbackQuery.data .replace('confirm_delete_location_', '') @@ -253,7 +269,7 @@ export default class AdminLocationHandler { await db.runAsync('COMMIT'); if (result.changes > 0) { - await this.bot.editMessageText( + await bot.editMessageText( `✅ Location deleted successfully!\n\nCountry: ${country}\nCity: ${city}\nDistrict: ${district}`, { chat_id: chatId, @@ -269,7 +285,7 @@ export default class AdminLocationHandler { } catch (error) { await db.runAsync('ROLLBACK'); console.error('Error deleting location:', error); - await this.bot.sendMessage( + await bot.sendMessage( chatId, '❌ Error deleting location. Please try again.', { @@ -281,8 +297,10 @@ export default class AdminLocationHandler { } } - async backToMenu(callbackQuery) { - if (!this.isAdmin(callbackQuery.from.id)) return; + static async backToMenu(callbackQuery) { + if (!this.isAdmin(callbackQuery.from.id)) { + return; + } const chatId = callbackQuery.message.chat.id; const messageId = callbackQuery.message.message_id; @@ -298,7 +316,7 @@ export default class AdminLocationHandler { } }; - await this.bot.editMessageText( + await bot.editMessageText( `You we're returned to the admin menu`, { chat_id: chatId, @@ -307,6 +325,6 @@ export default class AdminLocationHandler { } ); - this.userStates.delete(chatId); + userStates.delete(chatId); } } \ No newline at end of file diff --git a/src/handlers/adminProductHandler.js b/src/handlers/adminHandlers/adminProductHandler.js similarity index 67% rename from src/handlers/adminProductHandler.js rename to src/handlers/adminHandlers/adminProductHandler.js index 4851b63..684198e 100644 --- a/src/handlers/adminProductHandler.js +++ b/src/handlers/adminHandlers/adminProductHandler.js @@ -1,33 +1,30 @@ -import db from '../config/database.js'; -import config from '../config/config.js'; +import db from '../../config/database.js'; +import config from '../../config/config.js'; import fs from 'fs/promises'; -import User from "../models/User.js"; +import LocationService from "../../services/locationService.js"; +import bot from "../../context/bot.js"; +import CategoryService from "../../services/categoryService.js"; +import userStates from "../../context/userStates.js"; +import ProductService from "../../services/productService.js"; export default class AdminProductHandler { - constructor(bot) { - this.bot = bot; - this.userStates = new Map(); - } - - isAdmin(userId) { + static isAdmin(userId) { return config.ADMIN_IDS.includes(userId.toString()); } - async handleProductManagement(msg) { + static async handleProductManagement(msg) { const chatId = msg.chat?.id || msg.message?.chat.id; if (!this.isAdmin(msg.from?.id || msg.message?.from.id)) { - await this.bot.sendMessage(chatId, 'Unauthorized access.'); + await bot.sendMessage(chatId, 'Unauthorized access.'); return; } try { - const countries = await db.allAsync( - 'SELECT DISTINCT country FROM locations ORDER BY country' - ); + const countries = await LocationService.getCountries() if (countries.length === 0) { - await this.bot.sendMessage( + await bot.sendMessage( chatId, 'No locations available. Please add locations first.', { @@ -48,27 +45,28 @@ export default class AdminProductHandler { }]) }; - await this.bot.sendMessage( + await bot.sendMessage( chatId, '🌍 Select country to manage products:', {reply_markup: keyboard} ); } catch (error) { console.error('Error in handleProductManagement:', error); - await this.bot.sendMessage(chatId, 'Error loading locations. Please try again.'); + await bot.sendMessage(chatId, 'Error loading locations. Please try again.'); } } - async handleCountrySelection(callbackQuery) { + static async handleCountrySelection(callbackQuery) { + if (!this.isAdmin(callbackQuery.from.id)) { + return; + } + const chatId = callbackQuery.message.chat.id; const messageId = callbackQuery.message.message_id; const country = callbackQuery.data.replace('prod_country_', ''); try { - const cities = await db.allAsync( - 'SELECT DISTINCT city FROM locations WHERE country = ? ORDER BY city', - [country] - ); + const cities = await LocationService.getCitiesByCountry(country) const keyboard = { inline_keyboard: [ @@ -80,7 +78,7 @@ export default class AdminProductHandler { ] }; - await this.bot.editMessageText( + await bot.editMessageText( `🏙 Select city in ${country}:`, { chat_id: chatId, @@ -90,20 +88,21 @@ export default class AdminProductHandler { ); } catch (error) { console.error('Error in handleCountrySelection:', error); - await this.bot.sendMessage(chatId, 'Error loading cities. Please try again.'); + await bot.sendMessage(chatId, 'Error loading cities. Please try again.'); } } - async handleCitySelection(callbackQuery) { + static async handleCitySelection(callbackQuery) { + if (!this.isAdmin(callbackQuery.from.id)) { + return; + } + const chatId = callbackQuery.message.chat.id; const messageId = callbackQuery.message.message_id; const [country, city] = callbackQuery.data.replace('prod_city_', '').split('_'); try { - const districts = await db.allAsync( - 'SELECT district FROM locations WHERE country = ? AND city = ? ORDER BY district', - [country, city] - ); + const districts = await LocationService.getDistrictsByCountryAndCity(country, city); const keyboard = { inline_keyboard: [ @@ -115,7 +114,7 @@ export default class AdminProductHandler { ] }; - await this.bot.editMessageText( + await bot.editMessageText( `📍 Select district in ${city}:`, { chat_id: chatId, @@ -125,29 +124,29 @@ export default class AdminProductHandler { ); } catch (error) { console.error('Error in handleCitySelection:', error); - await this.bot.sendMessage(chatId, 'Error loading districts. Please try again.'); + await bot.sendMessage(chatId, 'Error loading districts. Please try again.'); } } - async handleDistrictSelection(callbackQuery) { + static async handleDistrictSelection(callbackQuery) { + if (!this.isAdmin(callbackQuery.from.id)) { + return; + } + const chatId = callbackQuery.message.chat.id; const messageId = callbackQuery.message.message_id; const [country, city, district] = callbackQuery.data.replace('prod_district_', '').split('_'); + userStates.delete(chatId); + try { - const location = await db.getAsync( - 'SELECT id FROM locations WHERE country = ? AND city = ? AND district = ?', - [country, city, district] - ); + const location = await LocationService.getLocation(country, city, district); if (!location) { throw new Error('Location not found'); } - const categories = await db.allAsync( - 'SELECT id, name FROM categories WHERE location_id = ? ORDER BY name', - [location.id] - ); + const categories = await CategoryService.getCategoriesByLocationId(location.id); const keyboard = { inline_keyboard: [ @@ -160,7 +159,7 @@ export default class AdminProductHandler { ] }; - await this.bot.editMessageText( + await bot.editMessageText( '📦 Select or add category:', { chat_id: chatId, @@ -170,15 +169,22 @@ export default class AdminProductHandler { ); } catch (error) { console.error('Error in handleDistrictSelection:', error); - await this.bot.sendMessage(chatId, 'Error loading categories. Please try again.'); + await bot.sendMessage(chatId, 'Error loading categories. Please try again.'); } } - async handleCategoryInput(msg) { + static async handleCategoryInput(msg) { const chatId = msg.chat.id; - const state = this.userStates.get(chatId); + const state = userStates.get(chatId); - if (!state || !state.action?.startsWith('add_category_')) return false; + if (!state || !state.action?.startsWith('add_category_')) { + return false; + } + + if (!this.isAdmin(msg.from.id)) { + await bot.sendMessage(chatId, 'Unauthorized access.'); + return; + } try { const locationId = state.action.replace('add_category_', ''); @@ -188,12 +194,9 @@ export default class AdminProductHandler { [locationId, msg.text] ); - const location = await db.getAsync( - 'SELECT country, city, district FROM locations WHERE id = ?', - [locationId] - ); + const location = await LocationService.getLocationById(locationId); - await this.bot.sendMessage( + await bot.sendMessage( chatId, `✅ Category "${msg.text}" added successfully!`, { @@ -208,52 +211,131 @@ export default class AdminProductHandler { } ); - this.userStates.delete(chatId); + userStates.delete(chatId); } catch (error) { if (error.code === 'SQLITE_CONSTRAINT') { - await this.bot.sendMessage(chatId, 'This category already exists in this location.'); + await bot.sendMessage(chatId, 'This category already exists in this location.'); } else { console.error('Error adding category:', error); - await this.bot.sendMessage(chatId, 'Error adding category. Please try again.'); + await bot.sendMessage(chatId, 'Error adding category. Please try again.'); } } return true; } - async handleAddCategory(callbackQuery) { + static async handleAddCategory(callbackQuery) { + if (!this.isAdmin(callbackQuery.from.id)) { + return; + } + const chatId = callbackQuery.message.chat.id; const locationId = callbackQuery.data.replace('add_category_', ''); - this.userStates.set(chatId, {action: `add_category_${locationId}`}); + userStates.set(chatId, {action: `add_category_${locationId}`}); - await this.bot.editMessageText( + const location = await LocationService.getLocationById(locationId); + + await bot.editMessageText( 'Please enter the name for the new category:', { chat_id: chatId, message_id: callbackQuery.message.message_id, reply_markup: { inline_keyboard: [[ - {text: '❌ Cancel', callback_data: `prod_district_${locationId}`} + {text: '❌ Cancel', callback_data: `prod_district_${location.country}_${location.city}_${location.district}`} ]] } } ); } + // Обновление категории + static async handleCategoryUpdate(msg) { + const chatId = msg.chat.id; + const state = userStates.get(chatId); + + if (!state || !state.action?.startsWith('edit_category_')) { + console.log('[DEBUG] Invalid state or action:', state); + return false; + } + + if (!this.isAdmin(msg.from.id)) { + await bot.sendMessage(chatId, 'Неавторизованный доступ.'); + return; + } + + try { + const [locationId, categoryId] = state.action.replace('edit_category_', '').split('_'); + + console.log('[DEBUG] Updating category:', { locationId, categoryId, newName: msg.text }); + + await db.runAsync( + 'UPDATE categories SET name = ? WHERE id = ? AND location_id = ?', + [msg.text, categoryId, locationId] + ); + + console.log('[DEBUG] Category updated successfully'); + + await bot.sendMessage( + chatId, + `✅ Название категории обновлено на "${msg.text}".`, + { + reply_markup: { + inline_keyboard: [[ + { + text: '« Назад к категориям', + callback_data: `prod_category_${locationId}_${categoryId}` + } + ]] + } + } + ); + + userStates.delete(chatId); + } catch (error) { + console.error('[ERROR] Error updating category:', error); + await bot.sendMessage(chatId, 'Ошибка обновления категории. Пожалуйста, попробуйте снова.'); + } + + return true; + } + // Редактирование категории + static async handleEditCategory(callbackQuery) { + if (!this.isAdmin(callbackQuery.from.id)) { + return; + } + + const chatId = callbackQuery.message.chat.id; + const [locationId, categoryId] = callbackQuery.data.replace('edit_category_', '').split('_'); + + userStates.set(chatId, { action: `edit_category_${locationId}_${categoryId}` }); + + await bot.editMessageText( + 'Пожалуйста, введите новое название категории:', + { + chat_id: chatId, + message_id: callbackQuery.message.message_id, + reply_markup: { + inline_keyboard: [[ + { text: '❌ Отмена', callback_data: `prod_category_${locationId}_${categoryId}` } + ]] + } + } + ); + } + static async handleCategorySelection(callbackQuery) { + if (!this.isAdmin(callbackQuery.from.id)) { + return; + } - async handleCategorySelection(callbackQuery) { const chatId = callbackQuery.message.chat.id; const messageId = callbackQuery.message.message_id; const [locationId, categoryId] = callbackQuery.data.replace('prod_category_', '').split('_'); try { - const subcategories = await db.allAsync( - 'SELECT id, name FROM subcategories WHERE category_id = ? ORDER BY name', - [categoryId] - ); - - const category = await db.getAsync('SELECT name FROM categories WHERE id = ?', [categoryId]); - const location = await db.getAsync('SELECT country, city, district FROM locations WHERE id = ?', [locationId]); + const subcategories = await CategoryService.getSubcategoriesByCategoryId(categoryId); + const category = await CategoryService.getCategoryById(categoryId); + const location = await LocationService.getLocationById(locationId); const keyboard = { inline_keyboard: [ @@ -270,7 +352,7 @@ export default class AdminProductHandler { ] }; - await this.bot.editMessageText( + await bot.editMessageText( `📦 Category: ${category.name}\nSelect or add subcategory:`, { chat_id: chatId, @@ -280,15 +362,22 @@ export default class AdminProductHandler { ); } catch (error) { console.error('Error in handleCategorySelection:', error); - await this.bot.sendMessage(chatId, 'Error loading subcategories. Please try again.'); + await bot.sendMessage(chatId, 'Error loading subcategories. Please try again.'); } } - async handleSubcategoryInput(msg) { + static async handleSubcategoryInput(msg) { const chatId = msg.chat.id; - const state = this.userStates.get(chatId); + const state = userStates.get(chatId); - if (!state || !state.action?.startsWith('add_subcategory_')) return false; + if (!state || !state.action?.startsWith('add_subcategory_')) { + return false; + } + + if (!this.isAdmin(msg.from.id)) { + await bot.sendMessage(chatId, 'Unauthorized access.'); + return; + } try { const [locationId, categoryId] = state.action.replace('add_subcategory_', '').split('_'); @@ -298,7 +387,7 @@ export default class AdminProductHandler { [categoryId, msg.text] ); - await this.bot.sendMessage( + await bot.sendMessage( chatId, `✅ Subcategory "${msg.text}" added successfully!`, { @@ -313,26 +402,30 @@ export default class AdminProductHandler { } ); - this.userStates.delete(chatId); + userStates.delete(chatId); } catch (error) { if (error.code === 'SQLITE_CONSTRAINT') { - await this.bot.sendMessage(chatId, 'This subcategory already exists in this category.'); + await bot.sendMessage(chatId, 'This subcategory already exists in this category.'); } else { console.error('Error adding subcategory:', error); - await this.bot.sendMessage(chatId, 'Error adding subcategory. Please try again.'); + await bot.sendMessage(chatId, 'Error adding subcategory. Please try again.'); } } return true; } - async handleAddSubcategory(callbackQuery) { + static async handleAddSubcategory(callbackQuery) { + if (!this.isAdmin(callbackQuery.from.id)) { + return; + } + const chatId = callbackQuery.message.chat.id; const [locationId, categoryId] = callbackQuery.data.replace('add_subcategory_', '').split('_'); - this.userStates.set(chatId, {action: `add_subcategory_${locationId}_${categoryId}`}); + userStates.set(chatId, {action: `add_subcategory_${locationId}_${categoryId}`}); - await this.bot.editMessageText( + await bot.editMessageText( 'Please enter the name for the new subcategory:', { chat_id: chatId, @@ -346,7 +439,7 @@ export default class AdminProductHandler { ); } - async viewProductsPage(locationId, categoryId, subcategoryId, page) { + static async viewProductsPage(locationId, categoryId, subcategoryId, page) { try { const limit = 10; const offset = (page || 0) * limit; @@ -424,27 +517,31 @@ export default class AdminProductHandler { } } - async handleSubcategorySelection(callbackQuery) { + static async handleSubcategorySelection(callbackQuery) { + if (!this.isAdmin(callbackQuery.from.id)) { + return; + } + const chatId = callbackQuery.message.chat.id; const messageId = callbackQuery.message.message_id; const [locationId, categoryId, subcategoryId] = callbackQuery.data.replace('prod_subcategory_', '').split('_'); - const state = this.userStates.get(chatId) + const state = userStates.get(chatId) for (const msgId of state?.msgToDelete || []) { try { - await this.bot.deleteMessage(chatId, msgId); + await bot.deleteMessage(chatId, msgId); } catch (e) { // ignore if can't delete } } - this.userStates.delete(chatId); + userStates.delete(chatId); try { const {text, markup} = await this.viewProductsPage(locationId, categoryId, subcategoryId, 0); - await this.bot.editMessageText( + await bot.editMessageText( text, { chat_id: chatId, @@ -454,11 +551,11 @@ export default class AdminProductHandler { ); } catch (error) { console.error('Error in handleSubcategorySelection:', error); - await this.bot.sendMessage(chatId, 'Error loading products. Please try again.'); + await bot.sendMessage(chatId, 'Error loading products. Please try again.'); } } - async handleProductListPage(callbackQuery) { + static async handleProductListPage(callbackQuery) { if (!this.isAdmin(callbackQuery.from.id)) { return; } @@ -469,7 +566,7 @@ export default class AdminProductHandler { try { const {text, markup} = await this.viewProductsPage(locationId, categoryId, subcategoryId, parseInt(page)); - await this.bot.editMessageText(text, { + await bot.editMessageText(text, { chat_id: chatId, message_id: callbackQuery.message.message_id, reply_markup: markup, @@ -480,19 +577,16 @@ export default class AdminProductHandler { } } - async handleAddProduct(callbackQuery) { + static async handleAddProduct(callbackQuery) { + if (!this.isAdmin(callbackQuery.from.id)) { + return; + } + const chatId = callbackQuery.message.chat.id; const messageId = callbackQuery.message.message_id; const [locationId, categoryId, subcategoryId] = callbackQuery.data.replace('add_product_', '').split('_'); try { - const location = await db.getAsync( - 'SELECT country, city, district FROM locations WHERE id = ?', - [locationId] - ); - const category = await db.getAsync('SELECT name FROM categories WHERE id = ?', [categoryId]); - const subcategory = await db.getAsync('SELECT name FROM subcategories WHERE id = ?', [subcategoryId]); - const sampleProducts = [ { name: "Sample Product 1", @@ -510,14 +604,14 @@ export default class AdminProductHandler { const jsonExample = JSON.stringify(sampleProducts, null, 2); const message = `To import products, send a JSON file with an array of products in the following format:\n\n
${jsonExample}\n\nEach product must have all the fields shown above.\n\nYou can either:\n1. Send the JSON as text\n2. Upload a .json file`;
- this.userStates.set(chatId, {
+ userStates.set(chatId, {
action: 'import_products',
locationId,
categoryId,
subcategoryId
});
- await this.bot.editMessageText(message, {
+ await bot.editMessageText(message, {
chat_id: chatId,
message_id: messageId,
parse_mode: 'HTML',
@@ -532,15 +626,22 @@ export default class AdminProductHandler {
});
} catch (error) {
console.error('Error in handleAddProduct:', error);
- await this.bot.sendMessage(chatId, 'Error preparing product import. Please try again.');
+ await bot.sendMessage(chatId, 'Error preparing product import. Please try again.');
}
}
- async handleProductImport(msg) {
+ static async handleProductImport(msg) {
const chatId = msg.chat.id;
- const state = this.userStates.get(chatId);
+ const state = userStates.get(chatId);
- if (!state || state.action !== 'import_products') return false;
+ if (!state || state.action !== 'import_products') {
+ return false;
+ }
+
+ if (!this.isAdmin(msg.from.id)) {
+ await bot.sendMessage(chatId, 'Unauthorized access.');
+ return;
+ }
try {
let products;
@@ -549,20 +650,20 @@ export default class AdminProductHandler {
// Handle file upload
if (msg.document) {
if (!msg.document.file_name.endsWith('.json')) {
- await this.bot.sendMessage(chatId, 'Please upload a .json file.');
+ await bot.sendMessage(chatId, 'Please upload a .json file.');
return true;
}
- const file = await this.bot.getFile(msg.document.file_id);
+ const file = await bot.getFile(msg.document.file_id);
- const fileContent = await this.bot.downloadFile(file.file_id, '.');
+ const fileContent = await bot.downloadFile(file.file_id, '.');
jsonContent = await fs.readFile(fileContent, 'utf8');
await fs.rm(fileContent);
} else if (msg.text) {
jsonContent = msg.text;
} else {
- await this.bot.sendMessage(chatId, 'Please send either a JSON file or JSON text.');
+ await bot.sendMessage(chatId, 'Please send either a JSON file or JSON text.');
return true;
}
@@ -572,7 +673,7 @@ export default class AdminProductHandler {
throw new Error('Input must be an array of products');
}
} catch (e) {
- await this.bot.sendMessage(chatId, 'Invalid JSON format. Please check the format and try again.');
+ await bot.sendMessage(chatId, 'Invalid JSON format. Please check the format and try again.');
return true;
}
@@ -597,7 +698,7 @@ export default class AdminProductHandler {
await db.runAsync('COMMIT');
- await this.bot.sendMessage(
+ await bot.sendMessage(
chatId,
`✅ Successfully imported ${products.length} products!`,
{
@@ -612,21 +713,28 @@ export default class AdminProductHandler {
}
);
- this.userStates.delete(chatId);
+ userStates.delete(chatId);
} catch (error) {
console.error('Error importing products:', error);
- await this.bot.sendMessage(chatId, 'Error importing products. Please check the data and try again.');
+ await bot.sendMessage(chatId, 'Error importing products. Please check the data and try again.');
await db.runAsync('ROLLBACK');
}
return true;
}
- async handleProductEditImport(msg) {
+ static async handleProductEditImport(msg) {
const chatId = msg.chat.id;
- const state = this.userStates.get(chatId);
+ const state = userStates.get(chatId);
- if (!state || state.action !== 'edit_product') return false;
+ if (!state || state.action !== 'edit_product') {
+ return false;
+ }
+
+ if (!this.isAdmin(msg.from.id)) {
+ await bot.sendMessage(chatId, 'Unauthorized access.');
+ return;
+ }
try {
let product;
@@ -635,34 +743,34 @@ export default class AdminProductHandler {
// Handle file upload
if (msg.document) {
if (!msg.document.file_name.endsWith('.json')) {
- await this.bot.sendMessage(chatId, 'Please upload a .json file.');
+ await bot.sendMessage(chatId, 'Please upload a .json file.');
return true;
}
- const file = await this.bot.getFile(msg.document.file_id);
+ const file = await bot.getFile(msg.document.file_id);
- const fileContent = await this.bot.downloadFile(file.file_id, '.');
+ const fileContent = await bot.downloadFile(file.file_id, '.');
jsonContent = await fs.readFile(fileContent, 'utf8');
await fs.rm(fileContent);
} else if (msg.text) {
jsonContent = msg.text;
} else {
- await this.bot.sendMessage(chatId, 'Please send either a JSON file or JSON text.');
+ await bot.sendMessage(chatId, 'Please send either a JSON file or JSON text.');
return true;
}
try {
product = JSON.parse(jsonContent);
} catch (e) {
- await this.bot.sendMessage(chatId, 'Invalid JSON format. Please check the format and try again.');
+ await bot.sendMessage(chatId, 'Invalid JSON format. Please check the format and try again.');
return true;
}
await db.runAsync('BEGIN TRANSACTION');
await db.runAsync(
- `UPDATE products SET
+ `UPDATE products SET
location_id = ?,
category_id = ?,
subcategory_id = ?,
@@ -678,17 +786,17 @@ export default class AdminProductHandler {
WHERE
id = ?
`,
- [
- state.locationId, state.categoryId, state.subcategoryId,
- product.name, product.price, product.description, product.private_data,
- product.quantity_in_stock, product.photo_url, product.hidden_photo_url,
- product.hidden_coordinates, product.hidden_description, state.productId
- ]
+ [
+ state.locationId, state.categoryId, state.subcategoryId,
+ product.name, product.price, product.description, product.private_data,
+ product.quantity_in_stock, product.photo_url, product.hidden_photo_url,
+ product.hidden_coordinates, product.hidden_description, state.productId
+ ]
);
await db.runAsync('COMMIT');
- await this.bot.sendMessage(
+ await bot.sendMessage(
chatId,
`✅ Successfully edited!`,
{
@@ -703,37 +811,38 @@ export default class AdminProductHandler {
}
);
- this.userStates.delete(chatId);
+ userStates.delete(chatId);
} catch (error) {
console.error('Error importing products:', error);
- await this.bot.sendMessage(chatId, 'Error importing products. Please check the data and try again.');
+ await bot.sendMessage(chatId, 'Error importing products. Please check the data and try again.');
await db.runAsync('ROLLBACK');
}
return true;
}
- async handleViewProduct(callbackQuery) {
+ static async handleViewProduct(callbackQuery) {
+ if (!this.isAdmin(callbackQuery.from.id)) {
+ return;
+ }
+
const chatId = callbackQuery.message.chat.id;
const messageId = callbackQuery.message.message_id;
const productId = callbackQuery.data.replace('view_product_', '');
try {
- const product = await db.getAsync(
- `SELECT p.*, c.name as category_name, s.name as subcategory_name,
- l.country, l.city, l.district
- FROM products p
- JOIN categories c ON p.category_id = c.id
- JOIN subcategories s ON p.subcategory_id = s.id
- JOIN locations l ON p.location_id = l.id
- WHERE p.id = ?`,
- [productId]
- );
+ const product = await ProductService.getDetailedProductById(productId)
if (!product) {
throw new Error('Product not found');
}
+ const location = await LocationService.getLocationById(product.location_id);
+
+ if (!location) {
+ throw new Error('Location not found');
+ }
+
const message = `
📦 Product Details:
@@ -741,7 +850,7 @@ Name: ${product.name}
Price: $${product.price}
Description: ${product.description}
Stock: ${product.quantity_in_stock}
-Location: ${product.country}, ${product.city}, ${product.district}
+Location: ${location.country}, ${location.city}, ${location.district}
Category: ${product.category_name}
Subcategory: ${product.subcategory_name}
@@ -770,47 +879,42 @@ Coordinates: ${product.hidden_coordinates}
// Send product photos
if (product.photo_url) {
try {
- photoMessage = await this.bot.sendPhoto(chatId, product.photo_url, {caption: 'Public photo'});
+ photoMessage = await bot.sendPhoto(chatId, product.photo_url, {caption: 'Public photo'});
} catch (e) {
- photoMessage = await this.bot.sendPhoto(chatId, "./corrupt-photo.jpg", {caption: 'Public photo'})
+ photoMessage = await bot.sendPhoto(chatId, "./corrupt-photo.jpg", {caption: 'Public photo'})
}
}
if (product.hidden_photo_url) {
try {
- hiddenPhotoMessage = await this.bot.sendPhoto(chatId, product.hidden_photo_url, {caption: 'Hidden photo'});
+ hiddenPhotoMessage = await bot.sendPhoto(chatId, product.hidden_photo_url, {caption: 'Hidden photo'});
} catch (e) {
- hiddenPhotoMessage = await this.bot.sendPhoto(chatId, "./corrupt-photo.jpg", {caption: 'Hidden photo'})
+ hiddenPhotoMessage = await bot.sendPhoto(chatId, "./corrupt-photo.jpg", {caption: 'Hidden photo'})
}
}
- this.userStates.set(chatId, {
+ userStates.set(chatId, {
msgToDelete: [photoMessage.message_id, hiddenPhotoMessage.message_id]
})
- await this.bot.deleteMessage(chatId, messageId);
- await this.bot.sendMessage(chatId, message, {reply_markup: keyboard});
+ await bot.deleteMessage(chatId, messageId);
+ await bot.sendMessage(chatId, message, {reply_markup: keyboard});
} catch (error) {
console.error('Error in handleViewProduct:', error);
- await this.bot.sendMessage(chatId, 'Error loading product details. Please try again.');
+ await bot.sendMessage(chatId, 'Error loading product details. Please try again.');
}
}
- async handleProductEdit(callbackQuery) {
+ static async handleProductEdit(callbackQuery) {
+ if (!this.isAdmin(callbackQuery.from.id)) {
+ return;
+ }
+
const chatId = callbackQuery.message.chat.id;
const messageId = callbackQuery.message.message_id;
const productId = callbackQuery.data.replace('edit_product_', '');
try {
- const product = await db.getAsync(
- `SELECT p.*, c.name as category_name, s.name as subcategory_name,
- l.country, l.city, l.district
- FROM products p
- JOIN categories c ON p.category_id = c.id
- JOIN subcategories s ON p.subcategory_id = s.id
- JOIN locations l ON p.location_id = l.id
- WHERE p.id = ?`,
- [productId]
- );
+ const product = await ProductService.getDetailedProductById(productId);
if (!product) {
throw new Error('Product not found');
@@ -835,7 +939,7 @@ Coordinates: ${product.hidden_coordinates}
const jsonExample = JSON.stringify(sampleProduct, null, 2);
const message = `To edit product, send a JSON file with product in the following format:\n\n${jsonExample}\n\nProduct must have all the fields shown above.\n\nYou can either:\n1. Send the JSON as text\n2. Upload a .json file`;
- this.userStates.set(chatId, {
+ userStates.set(chatId, {
action: 'edit_product',
locationId,
categoryId,
@@ -843,7 +947,7 @@ Coordinates: ${product.hidden_coordinates}
productId
});
- await this.bot.editMessageText(message, {
+ await bot.editMessageText(message, {
chat_id: chatId,
message_id: messageId,
parse_mode: 'HTML',
@@ -858,27 +962,20 @@ Coordinates: ${product.hidden_coordinates}
});
} catch (error) {
console.error('Error in handleViewProduct:', error);
- await this.bot.sendMessage(chatId, 'Error loading product details. Please try again.');
+ await bot.sendMessage(chatId, 'Error loading product details. Please try again.');
}
}
- async handleProductDelete(callbackQuery) {
- if (!this.isAdmin(callbackQuery.from.id)) return;
+ static async handleProductDelete(callbackQuery) {
+ if (!this.isAdmin(callbackQuery.from.id)) {
+ return;
+ }
const productId = callbackQuery.data.replace('delete_product_', '');
const chatId = callbackQuery.message.chat.id;
try {
- const product = await db.getAsync(
- `SELECT p.*, c.name as category_name, s.name as subcategory_name,
- l.country, l.city, l.district
- FROM products p
- JOIN categories c ON p.category_id = c.id
- JOIN subcategories s ON p.subcategory_id = s.id
- JOIN locations l ON p.location_id = l.id
- WHERE p.id = ?`,
- [productId]
- );
+ const product = await ProductService.getDetailedProductById(productId);
if (!product) {
throw new Error('Product not found');
@@ -896,7 +993,7 @@ Coordinates: ${product.hidden_coordinates}
]
};
- await this.bot.editMessageText(
+ await bot.editMessageText(
`⚠️ Are you sure you want to delete product\n\nThis action cannot be undone!`,
{
chat_id: chatId,
@@ -907,41 +1004,33 @@ Coordinates: ${product.hidden_coordinates}
);
} catch (error) {
console.error('Error in handleDeleteUser:', error);
- await this.bot.sendMessage(chatId, 'Error processing delete request. Please try again.');
+ await bot.sendMessage(chatId, 'Error processing delete request. Please try again.');
}
}
- async handleConfirmDelete(callbackQuery) {
- if (!this.isAdmin(callbackQuery.from.id)) return;
+ static async handleConfirmDelete(callbackQuery) {
+ if (!this.isAdmin(callbackQuery.from.id)) {
+ return;
+ }
const productId = callbackQuery.data.replace('confirm_delete_product_', '');
const chatId = callbackQuery.message.chat.id;
try {
- const product = await db.getAsync(
- `SELECT p.*, c.name as category_name, s.name as subcategory_name,
- l.country, l.city, l.district
- FROM products p
- JOIN categories c ON p.category_id = c.id
- JOIN subcategories s ON p.subcategory_id = s.id
- JOIN locations l ON p.location_id = l.id
- WHERE p.id = ?`,
- [productId]
- );
+ const product = await ProductService.getDetailedProductById(productId);
if (!product) {
throw new Error('Product not found');
}
-
try {
await db.runAsync('BEGIN TRANSACTION');
await db.runAsync('DELETE FROM products WHERE id=?', [productId.toString()]);
await db.runAsync('COMMIT');
} catch (e) {
await db.runAsync("ROLLBACK");
- console.error('Error deleting product:', error);
- throw error;
+ console.error('Error deleting product:', e);
+ throw e;
}
const keyboard = {
@@ -953,7 +1042,7 @@ Coordinates: ${product.hidden_coordinates}
]
};
- await this.bot.editMessageText(
+ await bot.editMessageText(
`✅ Product has been successfully deleted.`,
{
chat_id: chatId,
@@ -963,7 +1052,7 @@ Coordinates: ${product.hidden_coordinates}
);
} catch (error) {
console.error('Error in handleConfirmDelete:', error);
- await this.bot.sendMessage(chatId, 'Error deleting product. Please try again.');
+ await bot.sendMessage(chatId, 'Error deleting product. Please try again.');
}
}
}
\ No newline at end of file
diff --git a/src/handlers/adminUserHandler.js b/src/handlers/adminHandlers/adminUserHandler.js
similarity index 65%
rename from src/handlers/adminUserHandler.js
rename to src/handlers/adminHandlers/adminUserHandler.js
index a7b1542..9f11876 100644
--- a/src/handlers/adminUserHandler.js
+++ b/src/handlers/adminHandlers/adminUserHandler.js
@@ -1,18 +1,15 @@
-import User from '../models/User.js';
-import config from '../config/config.js';
-import db from '../config/database.js';
+import config from '../../config/config.js';
+import db from '../../config/database.js';
+import bot from "../../context/bot.js";
+import UserService from "../../services/userService.js";
+import userStates from "../../context/userStates.js";
export default class AdminUserHandler {
- constructor(bot) {
- this.bot = bot;
- this.userStates = new Map();
- }
-
- isAdmin(userId) {
+ static isAdmin(userId) {
return config.ADMIN_IDS.includes(userId.toString());
}
- async calculateStatistics() {
+ static async calculateStatistics() {
try {
const users = await db.allAsync(`
SELECT
@@ -48,7 +45,7 @@ export default class AdminUserHandler {
}
}
- async viewUserPage(page) {
+ static async viewUserPage(page) {
const limit = 10;
const offset = (page || 0) * limit;
@@ -101,17 +98,17 @@ export default class AdminUserHandler {
}
}
- async handleUserList(msg) {
+ static async handleUserList(msg) {
if (!this.isAdmin(msg.from.id)) {
- await this.bot.sendMessage(msg.chat.id, 'Unauthorized access.');
+ await bot.sendMessage(msg.chat.id, 'Unauthorized access.');
return;
}
const {text, markup} = await this.viewUserPage(0);
- await this.bot.sendMessage(msg.chat.id, text, {reply_markup: markup})
+ await bot.sendMessage(msg.chat.id, text, {reply_markup: markup})
}
- async handleUserListPage(callbackQuery) {
+ static async handleUserListPage(callbackQuery) {
if (!this.isAdmin(callbackQuery.from.id)) {
return;
}
@@ -121,7 +118,7 @@ export default class AdminUserHandler {
try {
const {text, markup} = await this.viewUserPage(page);
- await this.bot.editMessageText(text, {
+ await bot.editMessageText(text, {
chat_id: chatId,
message_id: callbackQuery.message.message_id,
reply_markup: markup,
@@ -132,37 +129,18 @@ export default class AdminUserHandler {
}
}
- async handleViewUser(callbackQuery) {
+ static async handleViewUser(callbackQuery) {
if (!this.isAdmin(callbackQuery.from.id)) return;
- const userId = callbackQuery.data.replace('view_user_', '');
+ const telegramId = callbackQuery.data.replace('view_user_', '');
const chatId = callbackQuery.message.chat.id;
try {
- const userStats = await db.getAsync(`
- SELECT
- u.*,
- COUNT(DISTINCT p.id) as purchase_count,
- COALESCE(SUM(p.total_price), 0) as total_spent,
- COUNT(DISTINCT cw.id) as wallet_count,
- COALESCE(SUM(t.amount), 0) as total_balance
- FROM users u
- LEFT JOIN purchases p ON u.id = p.user_id
- LEFT JOIN crypto_wallets cw ON u.id = cw.user_id
- LEFT JOIN transactions t ON u.id = t.user_id
- WHERE u.telegram_id = ?
- GROUP BY u.id
- `, [userId]);
+ const detailedUser = await UserService.getDetailedUserByTelegramId(telegramId);
+ const user = await UserService.getUserByTelegramId(telegramId);
- const user = await User.getById(userId);
-
- if (!user) {
- await this.bot.sendMessage(chatId, 'User not found.');
- return;
- }
-
- if (!userStats) {
- await this.bot.sendMessage(chatId, 'User not found.');
+ if (!detailedUser) {
+ await bot.sendMessage(chatId, 'User not found.');
return;
}
@@ -174,7 +152,7 @@ export default class AdminUserHandler {
WHERE u.telegram_id = ?
ORDER BY t.created_at DESC
LIMIT 5
- `, [userId]);
+ `, [telegramId]);
// Get recent purchases
const purchases = await db.allAsync(`
@@ -186,18 +164,18 @@ export default class AdminUserHandler {
WHERE u.telegram_id = ?
ORDER BY p.purchase_date DESC
LIMIT 5
- `, [userId]);
+ `, [telegramId]);
const message = `
👤 User Profile:
-ID: ${userId}
-📍 Location: ${userStats.country || 'Not set'}, ${userStats.city || 'Not set'}, ${userStats.district || 'Not set'}
+ID: ${telegramId}
+📍 Location: ${detailedUser.country || 'Not set'}, ${detailedUser.city || 'Not set'}, ${detailedUser.district || 'Not set'}
📊 Activity:
- - Total Purchases: ${userStats.purchase_count}
- - Total Spent: $${userStats.total_spent || 0}
- - Active Wallets: ${userStats.wallet_count}
+ - Total Purchases: ${detailedUser.purchase_count}
+ - Total Spent: $${detailedUser.total_spent || 0}
+ - Active Wallets: ${detailedUser.crypto_wallet_count}
- Bonus Balance: $${user.bonus_balance || 0}
- Total Balance: $${(user.total_balance || 0) + (user.bonus_balance || 0)}
@@ -207,24 +185,24 @@ ${transactions.map(t => ` • ${t.amount} ${t.wallet_type} (${t.tx_hash})`).joi
🛍 Recent Purchases:
${purchases.map(p => ` • ${p.product_name} x${p.quantity} - $${p.total_price}`).join('\n')}
-📅 Registered: ${new Date(userStats.created_at).toLocaleString()}
+📅 Registered: ${new Date(detailedUser.created_at).toLocaleString()}
`;
const keyboard = {
inline_keyboard: [
[
- {text: '💰 Edit Balance', callback_data: `edit_user_balance_${userId}`},
- {text: '📍 Edit Location', callback_data: `edit_user_location_${userId}`}
+ {text: '💰 Edit Balance', callback_data: `edit_user_balance_${telegramId}`},
+ {text: '📍 Edit Location', callback_data: `edit_user_location_${telegramId}`}
],
[
- {text: '🚫 Block User', callback_data: `block_user_${userId}`},
- {text: '❌ Delete User', callback_data: `delete_user_${userId}`}
+ {text: '🚫 Block User', callback_data: `block_user_${telegramId}`},
+ {text: '❌ Delete User', callback_data: `delete_user_${telegramId}`}
],
[{text: '« Back to User List', callback_data: `list_users_0`}]
]
};
- await this.bot.editMessageText(message, {
+ await bot.editMessageText(message, {
chat_id: chatId,
message_id: callbackQuery.message.message_id,
reply_markup: keyboard,
@@ -232,28 +210,30 @@ ${purchases.map(p => ` • ${p.product_name} x${p.quantity} - $${p.total_price}
});
} catch (error) {
console.error('Error in handleViewUser:', error);
- await this.bot.sendMessage(chatId, 'Error loading user details. Please try again.');
+ await bot.sendMessage(chatId, 'Error loading user details. Please try again.');
}
}
- async handleDeleteUser(callbackQuery) {
- if (!this.isAdmin(callbackQuery.from.id)) return;
+ static async handleDeleteUser(callbackQuery) {
+ if (!this.isAdmin(callbackQuery.from.id)) {
+ return;
+ }
- const userId = callbackQuery.data.replace('delete_user_', '');
+ const telegramId = callbackQuery.data.replace('delete_user_', '');
const chatId = callbackQuery.message.chat.id;
try {
const keyboard = {
inline_keyboard: [
[
- {text: '✅ Confirm Delete', callback_data: `confirm_delete_user_${userId}`},
- {text: '❌ Cancel', callback_data: `view_user_${userId}`}
+ {text: '✅ Confirm Delete', callback_data: `confirm_delete_user_${telegramId}`},
+ {text: '❌ Cancel', callback_data: `view_user_${telegramId}`}
]
]
};
- await this.bot.editMessageText(
- `⚠️ Are you sure you want to delete user ${userId}?\n\nThis action will:\n- Delete all user data\n- Remove all wallets\n- Erase purchase history\n\nThis action cannot be undone!`,
+ await bot.editMessageText(
+ `⚠️ Are you sure you want to delete user ${telegramId}?\n\nThis action will:\n- Delete all user data\n- Remove all wallets\n- Erase purchase history\n\nThis action cannot be undone!`,
{
chat_id: chatId,
message_id: callbackQuery.message.message_id,
@@ -263,18 +243,20 @@ ${purchases.map(p => ` • ${p.product_name} x${p.quantity} - $${p.total_price}
);
} catch (error) {
console.error('Error in handleDeleteUser:', error);
- await this.bot.sendMessage(chatId, 'Error processing delete request. Please try again.');
+ await bot.sendMessage(chatId, 'Error processing delete request. Please try again.');
}
}
- async handleConfirmDelete(callbackQuery) {
- if (!this.isAdmin(callbackQuery.from.id)) return;
+ static async handleConfirmDelete(callbackQuery) {
+ if (!this.isAdmin(callbackQuery.from.id)) {
+ return;
+ }
- const userId = callbackQuery.data.replace('confirm_delete_user_', '');
+ const telegramId = callbackQuery.data.replace('confirm_delete_user_', '');
const chatId = callbackQuery.message.chat.id;
try {
- await User.updateUserStatus(userId, 1);
+ await UserService.updateUserStatus(telegramId, 1);
const keyboard = {
inline_keyboard: [
@@ -283,13 +265,13 @@ ${purchases.map(p => ` • ${p.product_name} x${p.quantity} - $${p.total_price}
};
try {
- await this.bot.sendMessage(userId, '⚠️Your account has been deleted by administrator');
+ await bot.sendMessage(telegramId, '⚠️Your account has been deleted by administrator');
} catch (e) {
// ignore if we can't notify user
}
- await this.bot.editMessageText(
- `✅ User ${userId} has been successfully deleted.`,
+ await bot.editMessageText(
+ `✅ User ${telegramId} has been successfully deleted.`,
{
chat_id: chatId,
message_id: callbackQuery.message.message_id,
@@ -298,28 +280,30 @@ ${purchases.map(p => ` • ${p.product_name} x${p.quantity} - $${p.total_price}
);
} catch (error) {
console.error('Error in handleConfirmDelete:', error);
- await this.bot.sendMessage(chatId, 'Error deleting user. Please try again.');
+ await bot.sendMessage(chatId, 'Error deleting user. Please try again.');
}
}
- async handleBlockUser(callbackQuery) {
- if (!this.isAdmin(callbackQuery.from.id)) return;
+ static async handleBlockUser(callbackQuery) {
+ if (!this.isAdmin(callbackQuery.from.id)) {
+ return;
+ }
- const userId = callbackQuery.data.replace('block_user_', '');
+ const telegramId = callbackQuery.data.replace('block_user_', '');
const chatId = callbackQuery.message.chat.id;
try {
const keyboard = {
inline_keyboard: [
[
- {text: '✅ Confirm Block', callback_data: `confirm_block_user_${userId}`},
- {text: '❌ Cancel', callback_data: `view_user_${userId}`}
+ {text: '✅ Confirm Block', callback_data: `confirm_block_user_${telegramId}`},
+ {text: '❌ Cancel', callback_data: `view_user_${telegramId}`}
]
]
};
- await this.bot.editMessageText(
- `⚠️ Are you sure you want to block user ${userId}?`,
+ await bot.editMessageText(
+ `⚠️ Are you sure you want to block user ${telegramId}?`,
{
chat_id: chatId,
message_id: callbackQuery.message.message_id,
@@ -329,18 +313,20 @@ ${purchases.map(p => ` • ${p.product_name} x${p.quantity} - $${p.total_price}
);
} catch (error) {
console.error('Error in handleBlockUser:', error);
- await this.bot.sendMessage(chatId, 'Error processing block request. Please try again.');
+ await bot.sendMessage(chatId, 'Error processing block request. Please try again.');
}
}
- async handleConfirmBlock(callbackQuery) {
- if (!this.isAdmin(callbackQuery.from.id)) return;
+ static async handleConfirmBlock(callbackQuery) {
+ if (!this.isAdmin(callbackQuery.from.id)) {
+ return;
+ }
- const userId = callbackQuery.data.replace('confirm_block_user_', '');
+ const telegramId = callbackQuery.data.replace('confirm_block_user_', '');
const chatId = callbackQuery.message.chat.id;
try {
- await User.updateUserStatus(userId, 2);
+ await UserService.updateUserStatus(telegramId, 2);
const keyboard = {
inline_keyboard: [
@@ -349,13 +335,13 @@ ${purchases.map(p => ` • ${p.product_name} x${p.quantity} - $${p.total_price}
};
try {
- await this.bot.sendMessage(userId, '⚠️Your account has been blocked by administrator');
+ await bot.sendMessage(telegramId, '⚠️Your account has been blocked by administrator');
} catch (e) {
// ignore if we can't notify user
}
- await this.bot.editMessageText(
- `✅ User ${userId} has been successfully blocked.`,
+ await bot.editMessageText(
+ `✅ User ${telegramId} has been successfully blocked.`,
{
chat_id: chatId,
message_id: callbackQuery.message.message_id,
@@ -364,44 +350,48 @@ ${purchases.map(p => ` • ${p.product_name} x${p.quantity} - $${p.total_price}
);
} catch (error) {
console.error('Error in handleConfirmBlock:', error);
- await this.bot.sendMessage(chatId, 'Error blocking user. Please try again.');
+ await bot.sendMessage(chatId, 'Error blocking user. Please try again.');
}
}
- async handleEditUserBalance(callbackQuery) {
- if (!this.isAdmin(callbackQuery.from.id)) return;
+ static async handleEditUserBalance(callbackQuery) {
+ if (!this.isAdmin(callbackQuery.from.id)) {
+ return;
+ }
- const userId = callbackQuery.data.replace('edit_user_balance_', '');
+ const telegramId = callbackQuery.data.replace('edit_user_balance_', '');
const chatId = callbackQuery.message.chat.id;
try {
- const user = await User.getById(userId);
+ const user = await UserService.getUserByTelegramId(telegramId);
if (!user) {
- await this.bot.sendMessage(chatId, 'User not found.');
+ await bot.sendMessage(chatId, 'User not found.');
return;
}
- await this.bot.editMessageText(
- `Enter new value for bonus balance. \n\n👥 User: ${userId}\n💰 Bonus Balance Now: $${user.bonus_balance.toFixed(2)}`,
+ await bot.editMessageText(
+ `Enter new value for bonus balance. \n\n👥 User: ${telegramId}\n💰 Bonus Balance Now: $${user.bonus_balance.toFixed(2)}`,
{
chat_id: chatId,
message_id: callbackQuery.message.message_id,
}
);
- this.userStates.set(chatId, { action: "edit_bonus_balance", telegram_id: userId });
+ userStates.set(chatId, { action: "edit_bonus_balance", telegram_id: telegramId });
} catch (error) {
console.error('Error in handleEditUserBalance:', error);
- await this.bot.sendMessage(chatId, 'Error loading user wallets. Please try again.');
+ await bot.sendMessage(chatId, 'Error loading user wallets. Please try again.');
}
}
- async handleBonusBalanceInput(msg) {
- if (!this.isAdmin(msg.from.id)) return;
+ static async handleBonusBalanceInput(msg) {
+ if (!this.isAdmin(msg.from.id)) {
+ return;
+ }
const chatId = msg.chat.id;
- const state = this.userStates.get(chatId);
+ const state = userStates.get(chatId);
if (!state || state.action !== 'edit_bonus_balance') {
return false;
@@ -410,17 +400,17 @@ ${purchases.map(p => ` • ${p.product_name} x${p.quantity} - $${p.total_price}
const newValue = parseFloat(msg.text);
if (isNaN(newValue)) {
- await this.bot.sendMessage(chatId, 'Invalid value. Try again');
+ await bot.sendMessage(chatId, 'Invalid value. Try again');
return;
}
try {
await db.runAsync(`UPDATE users SET bonus_balance = ? WHERE telegram_id = ?`, [newValue, state.telegram_id])
- await this.bot.sendMessage(chatId, '✅ Done')
+ await bot.sendMessage(chatId, '✅ Done')
} catch (e) {
- await this.bot.sendMessage(chatId, 'Something went wrong');
+ await bot.sendMessage(chatId, 'Something went wrong');
}
- this.userStates.delete(chatId);
+ userStates.delete(chatId);
}
}
\ No newline at end of file
diff --git a/src/handlers/adminUserLocationHandler.js b/src/handlers/adminHandlers/adminUserLocationHandler.js
similarity index 68%
rename from src/handlers/adminUserLocationHandler.js
rename to src/handlers/adminHandlers/adminUserLocationHandler.js
index 42da9bd..ace57af 100644
--- a/src/handlers/adminUserLocationHandler.js
+++ b/src/handlers/adminHandlers/adminUserLocationHandler.js
@@ -1,27 +1,28 @@
-import db from '../config/database.js';
-import config from "../config/config.js";
+import db from '../../config/database.js';
+import config from "../../config/config.js";
+import LocationService from "../../services/locationService.js";
+import bot from "../../context/bot.js";
+import UserService from "../../services/userService.js";
export default class AdminUserLocationHandler {
- constructor(bot) {
- this.bot = bot;
- }
-
- isAdmin(userId) {
+ static isAdmin(userId) {
return config.ADMIN_IDS.includes(userId.toString());
}
- async handleEditUserLocation(callbackQuery) {
- if (!this.isAdmin(callbackQuery.from.id)) return;
+ static async handleEditUserLocation(callbackQuery) {
+ if (!this.isAdmin(callbackQuery.from.id)) {
+ return;
+ }
const userId = callbackQuery.data.replace('edit_user_location_', '');
const chatId = callbackQuery.message.chat.id;
const messageId = callbackQuery.message.message_id;
try {
- const countries = await db.allAsync('SELECT DISTINCT country FROM locations ORDER BY country');
+ const countries = await LocationService.getCountries();
if (countries.length === 0) {
- await this.bot.editMessageText(
+ await bot.editMessageText(
'No locations available yet.',
{
chat_id: chatId,
@@ -46,7 +47,7 @@ export default class AdminUserLocationHandler {
]
};
- await this.bot.editMessageText(
+ await bot.editMessageText(
'🌍 Select user country:',
{
chat_id: chatId,
@@ -56,22 +57,21 @@ export default class AdminUserLocationHandler {
);
} catch (error) {
console.error('Error in handleSetLocation:', error);
- await this.bot.sendMessage(chatId, 'Error loading countries. Please try again.');
+ await bot.sendMessage(chatId, 'Error loading countries. Please try again.');
}
}
- async handleEditUserCountry(callbackQuery) {
- if (!this.isAdmin(callbackQuery.from.id)) return;
+ static async handleEditUserCountry(callbackQuery) {
+ if (!this.isAdmin(callbackQuery.from.id)) {
+ return;
+ }
const chatId = callbackQuery.message.chat.id;
const messageId = callbackQuery.message.message_id;
const [country, userId] = callbackQuery.data.replace('edit_user_country_', '').split("_");
try {
- const cities = await db.allAsync(
- 'SELECT DISTINCT city FROM locations WHERE country = ? ORDER BY city',
- [country]
- );
+ const cities = await LocationService.getCitiesByCountry(country);
const keyboard = {
inline_keyboard: [
@@ -83,7 +83,7 @@ export default class AdminUserLocationHandler {
]
};
- await this.bot.editMessageText(
+ await bot.editMessageText(
`🏙 Select city in ${country}:`,
{
chat_id: chatId,
@@ -93,22 +93,21 @@ export default class AdminUserLocationHandler {
);
} catch (error) {
console.error('Error in handleSetCountry:', error);
- await this.bot.sendMessage(chatId, 'Error loading cities. Please try again.');
+ await bot.sendMessage(chatId, 'Error loading cities. Please try again.');
}
}
- async handleEditUserCity(callbackQuery) {
- if (!this.isAdmin(callbackQuery.from.id)) return;
+ static async handleEditUserCity(callbackQuery) {
+ if (!this.isAdmin(callbackQuery.from.id)) {
+ return;
+ }
const chatId = callbackQuery.message.chat.id;
const messageId = callbackQuery.message.message_id;
const [country, city, userId] = callbackQuery.data.replace('edit_user_city_', '').split('_');
try {
- const districts = await db.allAsync(
- 'SELECT district FROM locations WHERE country = ? AND city = ? ORDER BY district',
- [country, city]
- );
+ const districts = LocationService.getDistrictsByCountryAndCity(country, city)
const keyboard = {
inline_keyboard: [
@@ -120,7 +119,7 @@ export default class AdminUserLocationHandler {
]
};
- await this.bot.editMessageText(
+ await bot.editMessageText(
`📍 Select district in ${city}:`,
{
chat_id: chatId,
@@ -130,28 +129,25 @@ export default class AdminUserLocationHandler {
);
} catch (error) {
console.error('Error in handleSetCity:', error);
- await this.bot.sendMessage(chatId, 'Error loading districts. Please try again.');
+ await bot.sendMessage(chatId, 'Error loading districts. Please try again.');
}
}
- async handleEditUserDistrict(callbackQuery) {
- if (!this.isAdmin(callbackQuery.from.id)) return;
+ static async handleEditUserDistrict(callbackQuery) {
+ if (!this.isAdmin(callbackQuery.from.id)) {
+ return;
+ }
const chatId = callbackQuery.message.chat.id;
const messageId = callbackQuery.message.message_id;
- const [country, city, district, userId] = callbackQuery.data.replace('edit_user_district_', '').split('_');
+ const [country, city, district, telegramId] = callbackQuery.data.replace('edit_user_district_', '').split('_');
try {
await db.runAsync('BEGIN TRANSACTION');
-
- await db.runAsync(
- 'UPDATE users SET country = ?, city = ?, district = ? WHERE telegram_id = ?',
- [country, city, district, userId.toString()]
- );
-
+ await UserService.updateUserLocation(telegramId.toString(), country, city, district)
await db.runAsync('COMMIT');
- await this.bot.editMessageText(
+ await bot.editMessageText(
`✅ Location updated successfully!\n\nCountry: ${country}\nCity: ${city}\nDistrict: ${district}`,
{
chat_id: chatId,
@@ -166,7 +162,7 @@ export default class AdminUserLocationHandler {
} catch (error) {
await db.runAsync('ROLLBACK');
console.error('Error in handleSetDistrict:', error);
- await this.bot.sendMessage(chatId, 'Error updating location. Please try again.');
+ await bot.sendMessage(chatId, 'Error updating location. Please try again.');
}
}
}
\ No newline at end of file
diff --git a/src/handlers/userHandler.js b/src/handlers/userHandlers/userHandler.js
similarity index 57%
rename from src/handlers/userHandler.js
rename to src/handlers/userHandlers/userHandler.js
index 3dcac62..ebe8f9c 100644
--- a/src/handlers/userHandler.js
+++ b/src/handlers/userHandlers/userHandler.js
@@ -1,14 +1,11 @@
-import User from '../models/User.js';
-import config from "../config/config.js";
+import config from "../../config/config.js";
+import bot from "../../context/bot.js";
+import UserService from "../../services/userService.js";
export default class UserHandler {
- constructor(bot) {
- this.bot = bot;
- }
-
- async canUseBot(msg) {
- const userId = msg.from.id;
- const user = await User.getById(userId);
+ static async canUseBot(msg) {
+ const telegramId = msg.from.id;
+ const user = await UserService.getUserByTelegramId(telegramId);
const keyboard = {
inline_keyboard: [
@@ -20,25 +17,26 @@ export default class UserHandler {
case 0:
return true;
case 1:
- await this.bot.sendMessage(userId, '⚠️Your account has been deleted by administrator', {reply_markup: keyboard});
+ await bot.sendMessage(telegramId, '⚠️Your account has been deleted by administrator', {reply_markup: keyboard});
return false;
case 2:
- await this.bot.sendMessage(userId, '⚠️Your account has been blocked by administrator', {reply_markup: keyboard});
+ await bot.sendMessage(telegramId, '⚠️Your account has been blocked by administrator', {reply_markup: keyboard});
return false;
default:
- return true
+ return true;
}
}
- async showProfile(msg) {
+ static async showProfile(msg) {
const chatId = msg.chat.id;
- const userId = msg.from.id;
+ const telegramId = msg.from.id;
try {
- const userStats = await User.getUserStats(userId);
+ await UserService.recalculateUserBalanceByTelegramId(telegramId);
+ const userStats = await UserService.getDetailedUserByTelegramId(telegramId);
if (!userStats) {
- await this.bot.sendMessage(chatId, 'Profile not found. Please use /start to create one.');
+ await bot.sendMessage(chatId, 'Profile not found. Please use /start to create one.');
return;
}
@@ -49,14 +47,15 @@ export default class UserHandler {
const text = `
👤 *Your Profile*
-📱 Telegram ID: \`${userId}\`
+📱 Telegram ID: \`${telegramId}\`
📍 Location: ${locationText}
📊 Statistics:
├ Total Purchases: ${userStats.purchase_count || 0}
├ Total Spent: $${userStats.total_spent || 0}
├ Active Wallets: ${userStats.crypto_wallet_count || 0}
-└ Total Balance: $${userStats.total_balance || 0}
+├ Bonus Balance: $${userStats.bonus_balance || 0}
+└ Total Balance: $${(userStats.total_balance || 0) + (userStats.bonus_balance || 0)}
📅 Member since: ${new Date(userStats.created_at).toLocaleDateString()}
`;
@@ -68,24 +67,27 @@ export default class UserHandler {
]
};
- await this.bot.sendMessage(chatId, text, {
+ await bot.sendMessage(chatId, text, {
parse_mode: 'Markdown',
reply_markup: keyboard
});
} catch (error) {
console.error('Error in showProfile:', error);
- await this.bot.sendMessage(chatId, 'Error loading profile. Please try again.');
+ await bot.sendMessage(chatId, 'Error loading profile. Please try again.');
}
}
- async handleStart(msg) {
+ static async handleStart(msg) {
const chatId = msg.chat.id;
- const userId = msg.from.id;
+ const telegramId = msg.from.id;
const username = msg.chat.username;
try {
// Create user profile
- await User.create(userId, username);
+ await UserService.createUser({
+ telegram_id: telegramId,
+ username: username
+ });
const keyboard = {
reply_markup: {
@@ -97,22 +99,22 @@ export default class UserHandler {
}
};
- await this.bot.sendMessage(
+ await bot.sendMessage(
chatId,
'Welcome to the shop! Choose an option:',
keyboard
);
} catch (error) {
console.error('Error in handleStart:', error);
- await this.bot.sendMessage(chatId, 'Error creating user profile. Please try again.');
+ await bot.sendMessage(chatId, 'Error creating user profile. Please try again.');
}
}
- async handleBackToProfile(callbackQuery) {
+ static async handleBackToProfile(callbackQuery) {
await this.showProfile({
chat: {id: callbackQuery.message.chat.id},
from: {id: callbackQuery.from.id}
});
- await this.bot.deleteMessage(callbackQuery.message.chat.id, callbackQuery.message.message_id);
+ await bot.deleteMessage(callbackQuery.message.chat.id, callbackQuery.message.message_id);
}
}
\ No newline at end of file
diff --git a/src/handlers/userHandlers/userLocationHandler.js b/src/handlers/userHandlers/userLocationHandler.js
new file mode 100644
index 0000000..4547c81
--- /dev/null
+++ b/src/handlers/userHandlers/userLocationHandler.js
@@ -0,0 +1,147 @@
+import db from '../../config/database.js';
+import LocationService from "../../services/locationService.js";
+import bot from "../../context/bot.js";
+import UserService from "../../services/userService.js";
+
+export default class UserLocationHandler {
+ static async handleSetLocation(callbackQuery) {
+ const chatId = callbackQuery.message.chat.id;
+ const messageId = callbackQuery.message.message_id;
+
+ try {
+ const countries = await LocationService.getCountries();
+
+ if (countries.length === 0) {
+ await bot.editMessageText(
+ 'No locations available yet.',
+ {
+ chat_id: chatId,
+ message_id: messageId,
+ reply_markup: {
+ inline_keyboard: [[
+ {text: '« Back to Profile', callback_data: 'back_to_profile'}
+ ]]
+ }
+ }
+ );
+ return;
+ }
+
+ const keyboard = {
+ inline_keyboard: [
+ ...countries.map(loc => [{
+ text: loc.country,
+ callback_data: `set_country_${loc.country}`
+ }]),
+ [{text: '« Back to Profile', callback_data: 'back_to_profile'}]
+ ]
+ };
+
+ await bot.editMessageText(
+ '🌍 Select your country:',
+ {
+ chat_id: chatId,
+ message_id: messageId,
+ reply_markup: keyboard
+ }
+ );
+ } catch (error) {
+ console.error('Error in handleSetLocation:', error);
+ await bot.sendMessage(chatId, 'Error loading countries. Please try again.');
+ }
+ }
+
+ static async handleSetCountry(callbackQuery) {
+ const chatId = callbackQuery.message.chat.id;
+ const messageId = callbackQuery.message.message_id;
+ const country = callbackQuery.data.replace('set_country_', '');
+
+ try {
+ const cities = await LocationService.getCitiesByCountry(country);
+
+ const keyboard = {
+ inline_keyboard: [
+ ...cities.map(loc => [{
+ text: loc.city,
+ callback_data: `set_city_${country}_${loc.city}`
+ }]),
+ [{text: '« Back to Countries', callback_data: 'set_location'}]
+ ]
+ };
+
+ await bot.editMessageText(
+ `🏙 Select city in ${country}:`,
+ {
+ chat_id: chatId,
+ message_id: messageId,
+ reply_markup: keyboard
+ }
+ );
+ } catch (error) {
+ console.error('Error in handleSetCountry:', error);
+ await bot.sendMessage(chatId, 'Error loading cities. Please try again.');
+ }
+ }
+
+ static async handleSetCity(callbackQuery) {
+ const chatId = callbackQuery.message.chat.id;
+ const messageId = callbackQuery.message.message_id;
+ const [country, city] = callbackQuery.data.replace('set_city_', '').split('_');
+
+ try {
+ const districts = await LocationService.getDistrictsByCountryAndCity(country, city);
+
+ const keyboard = {
+ inline_keyboard: [
+ ...districts.map(loc => [{
+ text: loc.district,
+ callback_data: `set_district_${country}_${city}_${loc.district}`
+ }]),
+ [{text: '« Back to Cities', callback_data: `set_country_${country}`}]
+ ]
+ };
+
+ await bot.editMessageText(
+ `📍 Select district in ${city}:`,
+ {
+ chat_id: chatId,
+ message_id: messageId,
+ reply_markup: keyboard
+ }
+ );
+ } catch (error) {
+ console.error('Error in handleSetCity:', error);
+ await bot.sendMessage(chatId, 'Error loading districts. Please try again.');
+ }
+ }
+
+ static async handleSetDistrict(callbackQuery) {
+ const chatId = callbackQuery.message.chat.id;
+ const messageId = callbackQuery.message.message_id;
+ const telegramId = callbackQuery.from.id;
+ const [country, city, district] = callbackQuery.data.replace('set_district_', '').split('_');
+
+ try {
+ await db.runAsync('BEGIN TRANSACTION');
+ await UserService.updateUserLocation(telegramId, country, city, district);
+ await db.runAsync('COMMIT');
+
+ await bot.editMessageText(
+ `✅ Location updated successfully!\n\nCountry: ${country}\nCity: ${city}\nDistrict: ${district}`,
+ {
+ chat_id: chatId,
+ message_id: messageId,
+ reply_markup: {
+ inline_keyboard: [[
+ {text: '« Back to Profile', callback_data: 'back_to_profile'}
+ ]]
+ }
+ }
+ );
+ } catch (error) {
+ await db.runAsync('ROLLBACK');
+ console.error('Error in handleSetDistrict:', error);
+ await bot.sendMessage(chatId, 'Error updating location. Please try again.');
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/handlers/userProductHandler.js b/src/handlers/userHandlers/userProductHandler.js
similarity index 70%
rename from src/handlers/userProductHandler.js
rename to src/handlers/userHandlers/userProductHandler.js
index 2cd8c1c..5107030 100644
--- a/src/handlers/userProductHandler.js
+++ b/src/handlers/userHandlers/userProductHandler.js
@@ -1,32 +1,30 @@
-import db from '../config/database.js';
-import User from '../models/User.js';
-import WalletService from "../utils/walletService.js";
-import config from "../config/config.js";
+import db from '../../config/database.js';
+import config from "../../config/config.js";
+import LocationService from "../../services/locationService.js";
+import bot from "../../context/bot.js";
+import userStates from "../../context/userStates.js";
+import ProductService from "../../services/productService.js";
+import CategoryService from "../../services/categoryService.js";
+import UserService from "../../services/userService.js";
+import PurchaseService from "../../services/purchaseService.js";
export default class UserProductHandler {
- constructor(bot) {
- this.bot = bot;
- this.userStates = new Map();
- }
-
- async showProducts(msg) {
+ static async showProducts(msg) {
const chatId = msg.chat.id;
const messageId = msg?.message_id;
try {
- const countries = await db.allAsync(
- 'SELECT DISTINCT country FROM locations ORDER BY country'
- );
+ const countries = await LocationService.getCountries()
if (countries.length === 0) {
const message = 'No products available at the moment.';
if (messageId) {
- await this.bot.editMessageText(message, {
+ await bot.editMessageText(message, {
chat_id: chatId,
message_id: messageId
});
} else {
- await this.bot.sendMessage(chatId, message);
+ await bot.sendMessage(chatId, message);
}
return;
}
@@ -42,31 +40,28 @@ export default class UserProductHandler {
try {
if (messageId) {
- await this.bot.editMessageText(message, {
+ await bot.editMessageText(message, {
chat_id: chatId,
message_id: messageId,
reply_markup: keyboard
});
}
} catch (error) {
- await this.bot.sendMessage(chatId, message, {reply_markup: keyboard});
+ await bot.sendMessage(chatId, message, {reply_markup: keyboard});
}
} catch (error) {
console.error('Error in showProducts:', error);
- await this.bot.sendMessage(chatId, 'Error loading products. Please try again.');
+ await bot.sendMessage(chatId, 'Error loading products. Please try again.');
}
}
- async handleCountrySelection(callbackQuery) {
+ static async handleCountrySelection(callbackQuery) {
const chatId = callbackQuery.message.chat.id;
const messageId = callbackQuery.message.message_id;
const country = callbackQuery.data.replace('shop_country_', '');
try {
- const cities = await db.allAsync(
- 'SELECT DISTINCT city FROM locations WHERE country = ? ORDER BY city',
- [country]
- );
+ const cities = await LocationService.getCitiesByCountry(country);
const keyboard = {
inline_keyboard: [
@@ -78,7 +73,7 @@ export default class UserProductHandler {
]
};
- await this.bot.editMessageText(
+ await bot.editMessageText(
`🏙 Select city in ${country}:`,
{
chat_id: chatId,
@@ -88,20 +83,17 @@ export default class UserProductHandler {
);
} catch (error) {
console.error('Error in handleCountrySelection:', error);
- await this.bot.sendMessage(chatId, 'Error loading cities. Please try again.');
+ await bot.sendMessage(chatId, 'Error loading cities. Please try again.');
}
}
- async handleCitySelection(callbackQuery) {
+ static async handleCitySelection(callbackQuery) {
const chatId = callbackQuery.message.chat.id;
const messageId = callbackQuery.message.message_id;
const [country, city] = callbackQuery.data.replace('shop_city_', '').split('_');
try {
- const districts = await db.allAsync(
- 'SELECT district FROM locations WHERE country = ? AND city = ? ORDER BY district',
- [country, city]
- );
+ const districts = await LocationService.getDistrictsByCountryAndCity(country, city)
const keyboard = {
inline_keyboard: [
@@ -113,7 +105,7 @@ export default class UserProductHandler {
]
};
- await this.bot.editMessageText(
+ await bot.editMessageText(
`📍 Select district in ${city}:`,
{
chat_id: chatId,
@@ -123,32 +115,26 @@ export default class UserProductHandler {
);
} catch (error) {
console.error('Error in handleCitySelection:', error);
- await this.bot.sendMessage(chatId, 'Error loading districts. Please try again.');
+ await bot.sendMessage(chatId, 'Error loading districts. Please try again.');
}
}
- async handleDistrictSelection(callbackQuery) {
+ static async handleDistrictSelection(callbackQuery) {
const chatId = callbackQuery.message.chat.id;
const messageId = callbackQuery.message.message_id;
const [country, city, district] = callbackQuery.data.replace('shop_district_', '').split('_');
try {
- const location = await db.getAsync(
- 'SELECT id FROM locations WHERE country = ? AND city = ? AND district = ?',
- [country, city, district]
- );
+ const location = await LocationService.getLocation(country, city, district);
if (!location) {
throw new Error('Location not found');
}
- const categories = await db.allAsync(
- 'SELECT id, name FROM categories WHERE location_id = ? ORDER BY name',
- [location.id]
- );
+ const categories = await CategoryService.getCategoriesByLocationId(location.id);
if (categories.length === 0) {
- await this.bot.editMessageText(
+ await bot.editMessageText(
'No products available in this location yet.',
{
chat_id: chatId,
@@ -173,7 +159,7 @@ export default class UserProductHandler {
]
};
- await this.bot.editMessageText(
+ await bot.editMessageText(
'📦 Select category:',
{
chat_id: chatId,
@@ -183,28 +169,21 @@ export default class UserProductHandler {
);
} catch (error) {
console.error('Error in handleDistrictSelection:', error);
- await this.bot.sendMessage(chatId, 'Error loading categories. Please try again.');
+ await bot.sendMessage(chatId, 'Error loading categories. Please try again.');
}
}
- async handleCategorySelection(callbackQuery) {
+ static async handleCategorySelection(callbackQuery) {
const chatId = callbackQuery.message.chat.id;
const messageId = callbackQuery.message.message_id;
const [locationId, categoryId] = callbackQuery.data.replace('shop_category_', '').split('_');
try {
- const subcategories = await db.allAsync(
- 'SELECT id, name FROM subcategories WHERE category_id = ? ORDER BY name',
- [categoryId]
- );
-
- const location = await db.getAsync(
- 'SELECT country, city, district FROM locations WHERE id = ?',
- [locationId]
- );
+ const subcategories = await CategoryService.getSubcategoriesByCategoryId(categoryId);
+ const location = await LocationService.getLocationById(locationId);
if (subcategories.length === 0) {
- await this.bot.editMessageText(
+ await bot.editMessageText(
'No products available in this category yet.',
{
chat_id: chatId,
@@ -235,7 +214,7 @@ export default class UserProductHandler {
]
};
- await this.bot.editMessageText(
+ await bot.editMessageText(
'📦 Select subcategory:',
{
chat_id: chatId,
@@ -245,11 +224,11 @@ export default class UserProductHandler {
);
} catch (error) {
console.error('Error in handleCategorySelection:', error);
- await this.bot.sendMessage(chatId, 'Error loading subcategories. Please try again.');
+ await bot.sendMessage(chatId, 'Error loading subcategories. Please try again.');
}
}
- async handleSubcategorySelection(callbackQuery) {
+ static async handleSubcategorySelection(callbackQuery) {
const chatId = callbackQuery.message.chat.id;
const messageId = callbackQuery.message.message_id;
const [locationId, categoryId, subcategoryId, photoMessageId] = callbackQuery.data.replace('shop_subcategory_', '').split('_');
@@ -258,27 +237,17 @@ export default class UserProductHandler {
// Delete the photo message if it exists
if (photoMessageId) {
try {
- await this.bot.deleteMessage(chatId, photoMessageId);
+ await bot.deleteMessage(chatId, photoMessageId);
} catch (error) {
console.error('Error deleting photo message:', error);
}
}
- const products = await db.allAsync(
- `SELECT id, name, price, description, quantity_in_stock, photo_url
- FROM products
- WHERE location_id = ? AND category_id = ? AND subcategory_id = ?
- AND quantity_in_stock > 0
- ORDER BY name`,
- [locationId, categoryId, subcategoryId]
- );
-
- const location = await db.getAsync('SELECT * FROM locations WHERE id = ?', [locationId]);
- const category = await db.getAsync('SELECT name FROM categories WHERE id = ?', [categoryId]);
- const subcategory = await db.getAsync('SELECT name FROM subcategories WHERE id = ?', [subcategoryId]);
+ const products = await ProductService.getProductsByLocationAndCategory(locationId, categoryId, subcategoryId);
+ const subcategory = await CategoryService.getSubcategoryById(subcategoryId);
if (products.length === 0) {
- await this.bot.editMessageText(
+ await bot.editMessageText(
'No products available in this subcategory.',
{
chat_id: chatId,
@@ -306,7 +275,7 @@ export default class UserProductHandler {
]
};
- await this.bot.editMessageText(
+ await bot.editMessageText(
`📦 Products in ${subcategory.name}:`,
{
chat_id: chatId,
@@ -316,31 +285,24 @@ export default class UserProductHandler {
);
} catch (error) {
console.error('Error in handleSubcategorySelection:', error);
- await this.bot.sendMessage(chatId, 'Error loading products. Please try again.');
+ await bot.sendMessage(chatId, 'Error loading products. Please try again.');
}
}
- async handleProductSelection(callbackQuery) {
+ static async handleProductSelection(callbackQuery) {
const chatId = callbackQuery.message.chat.id;
const messageId = callbackQuery.message.message_id;
const productId = callbackQuery.data.replace('shop_product_', '');
try {
- const product = await db.getAsync(
- `SELECT p.*, c.name as category_name, s.name as subcategory_name
- FROM products p
- JOIN categories c ON p.category_id = c.id
- JOIN subcategories s ON p.subcategory_id = s.id
- WHERE p.id = ?`,
- [productId]
- );
+ const product = await ProductService.getDetailedProductById(productId);
if (!product) {
throw new Error('Product not found');
}
// Delete the previous message
- await this.bot.deleteMessage(chatId, messageId);
+ await bot.deleteMessage(chatId, messageId);
const message = `
📦 ${product.name}
@@ -359,9 +321,9 @@ Subcategory: ${product.subcategory_name}
let photoMessage;
if (product.photo_url) {
try {
- photoMessage = await this.bot.sendPhoto(chatId, product.photo_url, {caption: 'Public photo'});
+ photoMessage = await bot.sendPhoto(chatId, product.photo_url, {caption: 'Public photo'});
} catch (e) {
- photoMessage = await this.bot.sendPhoto(chatId, "./corrupt-photo.jpg", {caption: 'Public photo'})
+ photoMessage = await bot.sendPhoto(chatId, "./corrupt-photo.jpg", {caption: 'Public photo'})
}
}
@@ -389,13 +351,13 @@ Subcategory: ${product.subcategory_name}
};
// Then send the message with controls
- await this.bot.sendMessage(chatId, message, {
+ await bot.sendMessage(chatId, message, {
reply_markup: keyboard,
parse_mode: 'HTML'
});
// Store the current quantity and photo message ID in user state
- this.userStates.set(chatId, {
+ userStates.set(chatId, {
action: 'buying_product',
productId,
quantity: 1,
@@ -403,21 +365,18 @@ Subcategory: ${product.subcategory_name}
});
} catch (error) {
console.error('Error in handleProductSelection:', error);
- await this.bot.sendMessage(chatId, 'Error loading product details. Please try again.');
+ await bot.sendMessage(chatId, 'Error loading product details. Please try again.');
}
}
- async handleIncreaseQuantity(callbackQuery) {
+ static async handleIncreaseQuantity(callbackQuery) {
const chatId = callbackQuery.message.chat.id;
const messageId = callbackQuery.message.message_id;
const productId = callbackQuery.data.replace('increase_quantity_', '');
- const state = this.userStates.get(chatId);
+ const state = userStates.get(chatId);
try {
- const product = await db.getAsync(
- 'SELECT quantity_in_stock FROM products WHERE id = ?',
- [productId]
- );
+ const product = await ProductService.getProductById(productId);
if (!product) {
throw new Error('Product not found');
@@ -427,14 +386,14 @@ Subcategory: ${product.subcategory_name}
// If already at max stock, silently ignore
if (currentQuantity >= product.quantity_in_stock) {
- await this.bot.answerCallbackQuery(callbackQuery.id);
+ await bot.answerCallbackQuery(callbackQuery.id);
return;
}
const newQuantity = Math.min(currentQuantity + 1, product.quantity_in_stock);
// Update state
- this.userStates.set(chatId, {
+ userStates.set(chatId, {
...state,
quantity: newQuantity
});
@@ -455,7 +414,7 @@ Subcategory: ${product.subcategory_name}
}
];
- await this.bot.editMessageReplyMarkup(
+ await bot.editMessageReplyMarkup(
{inline_keyboard: keyboard},
{
chat_id: chatId,
@@ -463,24 +422,21 @@ Subcategory: ${product.subcategory_name}
}
);
- await this.bot.answerCallbackQuery(callbackQuery.id);
+ await bot.answerCallbackQuery(callbackQuery.id);
} catch (error) {
console.error('Error in handleIncreaseQuantity:', error);
- await this.bot.answerCallbackQuery(callbackQuery.id);
+ await bot.answerCallbackQuery(callbackQuery.id);
}
}
- async handleDecreaseQuantity(callbackQuery) {
+ static async handleDecreaseQuantity(callbackQuery) {
const chatId = callbackQuery.message.chat.id;
const messageId = callbackQuery.message.message_id;
const productId = callbackQuery.data.replace('decrease_quantity_', '');
- const state = this.userStates.get(chatId);
+ const state = userStates.get(chatId);
try {
- const product = await db.getAsync(
- 'SELECT quantity_in_stock FROM products WHERE id = ?',
- [productId]
- );
+ const product = await ProductService.getProductById(productId)
if (!product) {
throw new Error('Product not found');
@@ -490,14 +446,14 @@ Subcategory: ${product.subcategory_name}
// If already at minimum, silently ignore
if (currentQuantity <= 1) {
- await this.bot.answerCallbackQuery(callbackQuery.id);
+ await bot.answerCallbackQuery(callbackQuery.id);
return;
}
const newQuantity = Math.max(currentQuantity - 1, 1);
// Update state
- this.userStates.set(chatId, {
+ userStates.set(chatId, {
...state,
quantity: newQuantity
});
@@ -518,7 +474,7 @@ Subcategory: ${product.subcategory_name}
}
];
- await this.bot.editMessageReplyMarkup(
+ await bot.editMessageReplyMarkup(
{inline_keyboard: keyboard},
{
chat_id: chatId,
@@ -526,29 +482,26 @@ Subcategory: ${product.subcategory_name}
}
);
- await this.bot.answerCallbackQuery(callbackQuery.id);
+ await bot.answerCallbackQuery(callbackQuery.id);
} catch (error) {
console.error('Error in handleDecreaseQuantity:', error);
- await this.bot.answerCallbackQuery(callbackQuery.id);
+ await bot.answerCallbackQuery(callbackQuery.id);
}
}
- async handleBuyProduct(callbackQuery) {
+ static async handleBuyProduct(callbackQuery) {
const chatId = callbackQuery.message.chat.id;
- const userId = callbackQuery.from.id;
+ const telegramId = callbackQuery.from.id;
const productId = callbackQuery.data.replace('buy_product_', '');
- const state = this.userStates.get(chatId);
+ const state = userStates.get(chatId);
try {
- const user = await User.getById(userId);
+ const user = await UserService.getUserByTelegramId(telegramId)
if (!user) {
throw new Error('User not found');
}
- const product = await db.getAsync(
- 'SELECT * FROM products WHERE id = ?',
- [productId]
- );
+ const product = await ProductService.getProductById(productId);
if (!product) {
throw new Error('Product not found');
@@ -566,7 +519,7 @@ Subcategory: ${product.subcategory_name}
`, [user.id]);
if (cryptoWallets.length === 0) {
- await this.bot.sendMessage(
+ await bot.sendMessage(
chatId,
'You need to add a crypto wallet first to make purchases.',
{
@@ -590,7 +543,7 @@ Subcategory: ${product.subcategory_name}
]
};
- await this.bot.editMessageText(
+ await bot.editMessageText(
`🛒 Purchase Summary:\n\n` +
`Product: ${product.name}\n` +
`Quantity: ${quantity}\n` +
@@ -604,29 +557,25 @@ Subcategory: ${product.subcategory_name}
);
} catch (error) {
console.error('Error in handleBuyProduct:', error);
- await this.bot.sendMessage(chatId, 'Error processing purchase. Please try again.');
+ await bot.sendMessage(chatId, 'Error processing purchase. Please try again.');
}
}
- async handlePay(callbackQuery) {
+ static async handlePay(callbackQuery) {
const chatId = callbackQuery.message.chat.id;
- const userId = callbackQuery.from.id;
+ const telegramId = callbackQuery.from.id;
const [walletType, productId, quantity] = callbackQuery.data.replace('pay_with_', '').split('_');
- const state = this.userStates.get(chatId);
+ const state = userStates.get(chatId);
try {
- await User.recalculateBalance(userId);
- const user = await User.getById(userId);
+ await UserService.recalculateUserBalanceByTelegramId(telegramId);
+ const user = await UserService.getUserByTelegramId(telegramId)
if (!user) {
throw new Error('User not found');
}
- const product = await db.getAsync(
- 'SELECT * FROM products WHERE id = ?',
- [productId]
- );
-
+ const product = await ProductService.getProductById(productId);
if (!product) {
throw new Error('Product not found');
}
@@ -635,25 +584,22 @@ Subcategory: ${product.subcategory_name}
const balance = user.total_balance + user.bonus_balance;
if (totalPrice > balance) {
- this.userStates.delete(chatId);
- await this.bot.editMessageText(`Not enough money`, {
+ userStates.delete(chatId);
+ await bot.editMessageText(`Not enough money`, {
chat_id: chatId,
message_id: callbackQuery.message.message_id,
});
return;
}
- await db.runAsync(
- 'INSERT INTO purchases (user_id, product_id, wallet_type, tx_hash, quantity, total_price) VALUES (?, ?, ?, ?, ?, ?)',
- [user.id, product.id, walletType, "null", quantity, totalPrice]
- );
+ await PurchaseService.createPurchase(user.id, product.id, walletType, quantity, totalPrice)
let hiddenPhotoMessage;
if (product.hidden_photo_url) {
try {
- hiddenPhotoMessage = await this.bot.sendPhoto(chatId, product.hidden_photo_url, {caption: 'Hidden photo'});
+ hiddenPhotoMessage = await bot.sendPhoto(chatId, product.hidden_photo_url, {caption: 'Hidden photo'});
} catch (e) {
- hiddenPhotoMessage = await this.bot.sendPhoto(chatId, "./corrupt-photo.jpg", {caption: 'Hidden photo'})
+ hiddenPhotoMessage = await bot.sendPhoto(chatId, "./corrupt-photo.jpg", {caption: 'Hidden photo'})
}
}
@@ -681,11 +627,11 @@ Coordinates: ${product.hidden_coordinates}
]
};
- await this.bot.sendMessage(chatId, message, {reply_markup: keyboard});
- await this.bot.deleteMessage(chatId, callbackQuery.message.message_id);
+ await bot.sendMessage(chatId, message, {reply_markup: keyboard});
+ await bot.deleteMessage(chatId, callbackQuery.message.message_id);
} catch (error) {
console.error('Error in handleBuyProduct:', error);
- await this.bot.sendMessage(chatId, 'Error processing purchase. Please try again.');
+ await bot.sendMessage(chatId, 'Error processing purchase. Please try again.');
}
}
}
\ No newline at end of file
diff --git a/src/handlers/userPurchaseHandler.js b/src/handlers/userHandlers/userPurchaseHandler.js
similarity index 57%
rename from src/handlers/userPurchaseHandler.js
rename to src/handlers/userHandlers/userPurchaseHandler.js
index 92f5e04..ae7e5f0 100644
--- a/src/handlers/userPurchaseHandler.js
+++ b/src/handlers/userHandlers/userPurchaseHandler.js
@@ -1,15 +1,11 @@
-import db from '../config/database.js';
-import User from '../models/User.js';
-import WalletService from "../utils/walletService.js";
-import config from "../config/config.js";
+import config from "../../config/config.js";
+import PurchaseService from "../../services/purchaseService.js";
+import UserService from "../../services/userService.js";
+import bot from "../../context/bot.js";
+import ProductService from "../../services/productService.js";
export default class UserPurchaseHandler {
- constructor(bot) {
- this.bot = bot;
- this.userStates = new Map();
- }
-
- async viewPurchasePage(userId, page) {
+ static async viewPurchasePage(userId, page) {
try {
const limit = 10;
const offset = (page || 0) * limit;
@@ -17,22 +13,7 @@ export default class UserPurchaseHandler {
const previousPage = page > 0 ? page - 1 : 0;
const nextPage = page + 1;
- const purchases = await db.allAsync(`
- SELECT
- p.*,
- pr.name as product_name,
- pr.description,
- l.country,
- l.city,
- l.district
- FROM purchases p
- JOIN products pr ON p.product_id = pr.id
- JOIN locations l ON pr.location_id = l.id
- WHERE p.user_id = ?
- ORDER BY p.purchase_date DESC
- LIMIT ?
- OFFSET ?
- `, [userId, limit, offset]);
+ const purchases = await PurchaseService.getPurchasesByUserId(userId, limit, offset);
if ((purchases.length === 0) && (page == 0)) {
return {
@@ -76,22 +57,22 @@ export default class UserPurchaseHandler {
}
}
- async handlePurchaseListPage(callbackQuery) {
+ static async handlePurchaseListPage(callbackQuery) {
const telegramId = callbackQuery.from.id;
const chatId = callbackQuery.message.chat.id;
const page = callbackQuery.data.replace('list_purchases_', '');
try {
- const user = await User.getById(telegramId);
+ const user = await UserService.getUserByTelegramId(telegramId);
if (!user) {
- await this.bot.sendMessage(chatId, 'User not found.');
+ await bot.sendMessage(chatId, 'User not found.');
return;
}
const {text, markup} = await this.viewPurchasePage(user.id, parseInt(page));
- await this.bot.editMessageText(text, {
+ await bot.editMessageText(text, {
chat_id: chatId,
message_id: callbackQuery.message.message_id,
reply_markup: markup,
@@ -102,70 +83,52 @@ export default class UserPurchaseHandler {
}
}
- async showPurchases(msg) {
- // const userId = callbackQuery.from.id;
- // const chatId = callbackQuery.message.chat.id;
-
+ static async showPurchases(msg) {
const chatId = msg.chat.id;
const telegramId = msg.from.id;
try {
- const user = await User.getById(telegramId);
+ const user = await UserService.getUserByTelegramId(telegramId);
if (!user) {
- await this.bot.sendMessage(chatId, 'User not found.');
+ await bot.sendMessage(chatId, 'User not found.');
return;
}
const {text, markup} = await this.viewPurchasePage(user.id, 0);
- await this.bot.sendMessage(chatId, text, {reply_markup: markup, parse_mode: 'Markdown'});
+ await bot.sendMessage(chatId, text, {reply_markup: markup, parse_mode: 'Markdown'});
} catch (error) {
console.error('Error in handleSubcategorySelection:', error);
- await this.bot.sendMessage(chatId, 'Error loading products. Please try again.');
+ await bot.sendMessage(chatId, 'Error loading products. Please try again.');
}
}
- async viewPurchase(callbackQuery) {
+ static async viewPurchase(callbackQuery) {
const chatId = callbackQuery.message.chat.id;
const purchaseId = callbackQuery.data.replace('view_purchase_', '');
- const purchase = await db.getAsync(`
- SELECT
- p.*,
- pr.name as product_name,
- pr.description,
- l.country,
- l.city,
- l.district
- FROM purchases p
- JOIN products pr ON p.product_id = pr.id
- JOIN locations l ON pr.location_id = l.id
- WHERE p.id = ?
- `, [purchaseId]);
+ const purchase = await PurchaseService.getPurchaseById(purchaseId);
if (!purchase) {
- await this.bot.sendMessage(chatId, "No such purchase");
+ await bot.sendMessage(chatId, "No such purchase");
return;
}
- const product = await db.getAsync(
- `SELECT * FROM products WHERE id = ?`,
- [purchase.product_id]
- );
+ const product = await ProductService.getProductById(purchase.product_id)
if (!product) {
- await this.bot.sendMessage(chatId, "No such product");
+ await bot.sendMessage(chatId, "No such product");
return;
}
let hiddenPhotoMessage;
if (product.hidden_photo_url) {
try {
- hiddenPhotoMessage = await this.bot.sendPhoto(chatId, product.hidden_photo_url, {caption: 'Hidden photo'});
+ hiddenPhotoMessage = await bot.sendPhoto(chatId, product.hidden_photo_url, {caption: 'Hidden photo'});
} catch (e) {
- hiddenPhotoMessage = await this.bot.sendPhoto(chatId, "./corrupt-photo.jpg", {caption: 'Hidden photo'})
+ hiddenPhotoMessage = await bot.sendPhoto(chatId, "./corrupt-photo.jpg", {caption: 'Hidden photo'})
}
}
@@ -191,7 +154,7 @@ Coordinates: ${product.hidden_coordinates}
]
};
- await this.bot.sendMessage(chatId, message, {reply_markup: keyboard});
- await this.bot.deleteMessage(chatId, callbackQuery.message.message_id);
+ await bot.sendMessage(chatId, message, {reply_markup: keyboard});
+ await bot.deleteMessage(chatId, callbackQuery.message.message_id);
}
}
diff --git a/src/handlers/userWalletsHandler.js b/src/handlers/userHandlers/userWalletsHandler.js
similarity index 86%
rename from src/handlers/userWalletsHandler.js
rename to src/handlers/userHandlers/userWalletsHandler.js
index 6f3a0a3..8fd38e8 100644
--- a/src/handlers/userWalletsHandler.js
+++ b/src/handlers/userHandlers/userWalletsHandler.js
@@ -1,23 +1,19 @@
-import db from '../config/database.js';
-import User from '../models/User.js';
-import WalletGenerator from '../utils/walletGenerator.js';
-import WalletService from '../utils/walletService.js';
+import db from '../../config/database.js';
+import WalletGenerator from '../../utils/walletGenerator.js';
+import WalletService from '../../utils/walletService.js';
+import UserService from "../../services/userService.js";
+import bot from "../../context/bot.js";
export default class UserWalletsHandler {
- constructor(bot) {
- this.bot = bot;
- this.userStates = new Map();
- }
-
- async showBalance(msg) {
+ static async showBalance(msg) {
const chatId = msg.chat.id;
- const userId = msg.from.id;
+ const telegramId = msg.from.id;
try {
- const user = await db.getAsync('SELECT id FROM users WHERE telegram_id = ?', [userId.toString()]);
+ const user = await UserService.getUserByTelegramId(telegramId.toString());
if (!user) {
- await this.bot.sendMessage(chatId, 'Profile not found. Please use /start to create one.');
+ await bot.sendMessage(chatId, 'Profile not found. Please use /start to create one.');
return;
}
@@ -92,16 +88,17 @@ export default class UserWalletsHandler {
]);
}
- await this.bot.sendMessage(chatId, message, {
+ await bot.sendMessage(chatId, message, {
reply_markup: keyboard,
parse_mode: 'Markdown'
});
} catch (error) {
console.error('Error in showBalance:', error);
- await this.bot.sendMessage(chatId, 'Error loading balance. Please try again.');
+ await bot.sendMessage(chatId, 'Error loading balance. Please try again.');
}
}
- async handleAddWallet(callbackQuery) {
+
+ static async handleAddWallet(callbackQuery) {
const chatId = callbackQuery.message.chat.id;
const cryptoOptions = [
@@ -122,7 +119,7 @@ export default class UserWalletsHandler {
]
};
- await this.bot.editMessageText(
+ await bot.editMessageText(
'🔐 Select cryptocurrency to generate wallet:',
{
chat_id: chatId,
@@ -131,13 +128,15 @@ export default class UserWalletsHandler {
}
);
}
- async handleGenerateWallet(callbackQuery) {
+
+ static async handleGenerateWallet(callbackQuery) {
const chatId = callbackQuery.message.chat.id;
- const userId = callbackQuery.from.id;
+ const telegramId = callbackQuery.from.id;
const walletType = callbackQuery.data.replace('generate_wallet_', '').replace('_', ' ');
try {
- const user = await User.getById(userId);
+ const user = await UserService.getUserByTelegramId(telegramId);
+
if (!user) {
throw new Error('User not found');
}
@@ -148,7 +147,7 @@ export default class UserWalletsHandler {
// Generate new wallets
const mnemonic = await WalletGenerator.generateMnemonic();
const wallets = await WalletGenerator.generateWallets(mnemonic);
- const encryptedMnemonic = await WalletGenerator.encryptMnemonic(mnemonic, userId);
+ const encryptedMnemonic = await WalletGenerator.encryptMnemonic(mnemonic, telegramId);
// Get the base wallet type (ETH for ERC-20, TRON for TRC-20)
const baseType = this.getBaseWalletType(walletType);
@@ -197,7 +196,7 @@ export default class UserWalletsHandler {
message += `\n⚠️ Important: Your recovery phrase has been securely stored. Keep your wallet address safe!`;
- await this.bot.editMessageText(message, {
+ await bot.editMessageText(message, {
chat_id: chatId,
message_id: callbackQuery.message.message_id,
parse_mode: 'Markdown',
@@ -215,7 +214,7 @@ export default class UserWalletsHandler {
}
} catch (error) {
console.error('Error generating wallet:', error);
- await this.bot.editMessageText(
+ await bot.editMessageText(
'❌ Error generating wallet. Please try again.',
{
chat_id: chatId,
@@ -229,12 +228,13 @@ export default class UserWalletsHandler {
);
}
}
- async handleTopUpWallet(callbackQuery) {
+
+ static async handleTopUpWallet(callbackQuery) {
const chatId = callbackQuery.message.chat.id;
- const userId = callbackQuery.from.id;
+ const telegramId = callbackQuery.from.id;
try {
- const user = await db.getAsync('SELECT id FROM users WHERE telegram_id = ?', [userId.toString()]);
+ const user = await UserService.getUserByTelegramId(telegramId);
// Get crypto wallets
const cryptoWallets = await db.allAsync(`
@@ -245,7 +245,7 @@ export default class UserWalletsHandler {
`, [user.id]);
if (cryptoWallets.length === 0) {
- await this.bot.editMessageText(
+ await bot.editMessageText(
'You don\'t have any wallets yet.',
{
chat_id: chatId,
@@ -295,7 +295,7 @@ export default class UserWalletsHandler {
]
};
- await this.bot.editMessageText(message, {
+ await bot.editMessageText(message, {
chat_id: chatId,
message_id: callbackQuery.message.message_id,
parse_mode: 'Markdown',
@@ -303,15 +303,16 @@ export default class UserWalletsHandler {
});
} catch (error) {
console.error('Error in handleTopUpWallet:', error);
- await this.bot.sendMessage(chatId, 'Error loading wallets. Please try again.');
+ await bot.sendMessage(chatId, 'Error loading wallets. Please try again.');
}
}
- async handleWalletHistory(callbackQuery) {
+
+ static async handleWalletHistory(callbackQuery) {
const chatId = callbackQuery.message.chat.id;
- const userId = callbackQuery.from.id;
+ const telegramId = callbackQuery.from.id;
try {
- const user = await db.getAsync('SELECT id FROM users WHERE telegram_id = ?', [userId.toString()]);
+ const user = UserService.getUserByTelegramId(telegramId);
const transactions = await db.allAsync(`
SELECT type, amount, tx_hash, created_at, wallet_type
@@ -322,7 +323,7 @@ export default class UserWalletsHandler {
`, [user.id]);
if (transactions.length === 0) {
- await this.bot.editMessageText(
+ await bot.editMessageText(
'No transactions found.',
{
chat_id: chatId,
@@ -346,7 +347,7 @@ export default class UserWalletsHandler {
message += `🕒 ${date}\n\n`;
});
- await this.bot.editMessageText(message, {
+ await bot.editMessageText(message, {
chat_id: chatId,
message_id: callbackQuery.message.message_id,
parse_mode: 'Markdown',
@@ -358,15 +359,16 @@ export default class UserWalletsHandler {
});
} catch (error) {
console.error('Error in handleWalletHistory:', error);
- await this.bot.sendMessage(chatId, 'Error loading transaction history. Please try again.');
+ await bot.sendMessage(chatId, 'Error loading transaction history. Please try again.');
}
}
- async handleViewArchivedWallets(callbackQuery) {
+
+ static async handleViewArchivedWallets(callbackQuery) {
const chatId = callbackQuery.message.chat.id;
- const userId = callbackQuery.from.id;
+ const telegramId = callbackQuery.from.id;
try {
- const user = await db.getAsync('SELECT id FROM users WHERE telegram_id = ?', [userId.toString()]);
+ const user = await UserService.getUserByTelegramId(telegramId.toString());
// Get archived wallets and validate timestamps
const archivedWallets = await db.allAsync(`
@@ -384,7 +386,7 @@ export default class UserWalletsHandler {
});
if (validArchivedWallets.length === 0) {
- await this.bot.editMessageText(
+ await bot.editMessageText(
'No archived wallets found.',
{
chat_id: chatId,
@@ -482,7 +484,7 @@ export default class UserWalletsHandler {
message += `💰 *Total Value of Archived Wallets:* $${totalUsdValue.toFixed(2)}`;
- await this.bot.editMessageText(message, {
+ await bot.editMessageText(message, {
chat_id: chatId,
message_id: callbackQuery.message.message_id,
parse_mode: 'Markdown',
@@ -494,15 +496,16 @@ export default class UserWalletsHandler {
});
} catch (error) {
console.error('Error in handleViewArchivedWallets:', error);
- await this.bot.sendMessage(chatId, 'Error loading archived wallets. Please try again.');
+ await bot.sendMessage(chatId, 'Error loading archived wallets. Please try again.');
}
}
- async handleRefreshBalance(callbackQuery) {
+
+ static async handleRefreshBalance(callbackQuery) {
const chatId = callbackQuery.message.chat.id;
const messageId = callbackQuery.message.message_id;
try {
- await this.bot.editMessageText(
+ await bot.editMessageText(
'🔄 Refreshing balances...',
{
chat_id: chatId,
@@ -517,10 +520,10 @@ export default class UserWalletsHandler {
});
// Delete the "refreshing" message
- await this.bot.deleteMessage(chatId, messageId);
+ await bot.deleteMessage(chatId, messageId);
} catch (error) {
console.error('Error in handleRefreshBalance:', error);
- await this.bot.editMessageText(
+ await bot.editMessageText(
'❌ Error refreshing balances. Please try again.',
{
chat_id: chatId,
@@ -534,21 +537,23 @@ export default class UserWalletsHandler {
);
}
}
- async handleBackToBalance(callbackQuery) {
+
+ static async handleBackToBalance(callbackQuery) {
await this.showBalance({
chat: { id: callbackQuery.message.chat.id },
from: { id: callbackQuery.from.id }
});
- await this.bot.deleteMessage(callbackQuery.message.chat.id, callbackQuery.message.message_id);
+ await bot.deleteMessage(callbackQuery.message.chat.id, callbackQuery.message.message_id);
}
// Helper methods
- getBaseWalletType(walletType) {
+ static getBaseWalletType(walletType) {
if (walletType.includes('TRC-20')) return 'TRON';
if (walletType.includes('ERC-20')) return 'ETH';
return walletType;
}
- getWalletAddress(wallets, walletType) {
+
+ static getWalletAddress(wallets, walletType) {
if (walletType.includes('TRC-20')) return wallets.TRON.address;
if (walletType.includes('ERC-20')) return wallets.ETH.address;
if (walletType === 'BTC') return wallets.BTC.address;
@@ -556,7 +561,8 @@ export default class UserWalletsHandler {
if (walletType === 'ETH') return wallets.ETH.address;
throw new Error('Invalid wallet type');
}
- getNetworkName(walletType) {
+
+ static getNetworkName(walletType) {
if (walletType.includes('TRC-20')) return 'Tron Network (TRC-20)';
if (walletType.includes('ERC-20')) return 'Ethereum Network (ERC-20)';
if (walletType === 'BTC') return 'Bitcoin Network';
diff --git a/src/handlers/userLocationHandler.js b/src/handlers/userLocationHandler.js
deleted file mode 100644
index b57fb8d..0000000
--- a/src/handlers/userLocationHandler.js
+++ /dev/null
@@ -1,160 +0,0 @@
-import db from '../config/database.js';
-import Validators from '../utils/validators.js';
-
-export default class UserLocationHandler {
- constructor(bot) {
- this.bot = bot;
- }
-
- async handleSetLocation(callbackQuery) {
- const chatId = callbackQuery.message.chat.id;
- const messageId = callbackQuery.message.message_id;
-
- try {
- const countries = await db.allAsync('SELECT DISTINCT country FROM locations ORDER BY country');
-
- if (countries.length === 0) {
- await this.bot.editMessageText(
- 'No locations available yet.',
- {
- chat_id: chatId,
- message_id: messageId,
- reply_markup: {
- inline_keyboard: [[
- { text: '« Back to Profile', callback_data: 'back_to_profile' }
- ]]
- }
- }
- );
- return;
- }
-
- const keyboard = {
- inline_keyboard: [
- ...countries.map(loc => [{
- text: loc.country,
- callback_data: `set_country_${loc.country}`
- }]),
- [{ text: '« Back to Profile', callback_data: 'back_to_profile' }]
- ]
- };
-
- await this.bot.editMessageText(
- '🌍 Select your country:',
- {
- chat_id: chatId,
- message_id: messageId,
- reply_markup: keyboard
- }
- );
- } catch (error) {
- console.error('Error in handleSetLocation:', error);
- await this.bot.sendMessage(chatId, 'Error loading countries. Please try again.');
- }
- }
-
- async handleSetCountry(callbackQuery) {
- const chatId = callbackQuery.message.chat.id;
- const messageId = callbackQuery.message.message_id;
- const country = callbackQuery.data.replace('set_country_', '');
-
- try {
- const cities = await db.allAsync(
- 'SELECT DISTINCT city FROM locations WHERE country = ? ORDER BY city',
- [country]
- );
-
- const keyboard = {
- inline_keyboard: [
- ...cities.map(loc => [{
- text: loc.city,
- callback_data: `set_city_${country}_${loc.city}`
- }]),
- [{ text: '« Back to Countries', callback_data: 'set_location' }]
- ]
- };
-
- await this.bot.editMessageText(
- `🏙 Select city in ${country}:`,
- {
- chat_id: chatId,
- message_id: messageId,
- reply_markup: keyboard
- }
- );
- } catch (error) {
- console.error('Error in handleSetCountry:', error);
- await this.bot.sendMessage(chatId, 'Error loading cities. Please try again.');
- }
- }
-
- async handleSetCity(callbackQuery) {
- const chatId = callbackQuery.message.chat.id;
- const messageId = callbackQuery.message.message_id;
- const [country, city] = callbackQuery.data.replace('set_city_', '').split('_');
-
- try {
- const districts = await db.allAsync(
- 'SELECT district FROM locations WHERE country = ? AND city = ? ORDER BY district',
- [country, city]
- );
-
- const keyboard = {
- inline_keyboard: [
- ...districts.map(loc => [{
- text: loc.district,
- callback_data: `set_district_${country}_${city}_${loc.district}`
- }]),
- [{ text: '« Back to Cities', callback_data: `set_country_${country}` }]
- ]
- };
-
- await this.bot.editMessageText(
- `📍 Select district in ${city}:`,
- {
- chat_id: chatId,
- message_id: messageId,
- reply_markup: keyboard
- }
- );
- } catch (error) {
- console.error('Error in handleSetCity:', error);
- await this.bot.sendMessage(chatId, 'Error loading districts. Please try again.');
- }
- }
-
- async handleSetDistrict(callbackQuery) {
- const chatId = callbackQuery.message.chat.id;
- const messageId = callbackQuery.message.message_id;
- const userId = callbackQuery.from.id;
- const [country, city, district] = callbackQuery.data.replace('set_district_', '').split('_');
-
- try {
- await db.runAsync('BEGIN TRANSACTION');
-
- await db.runAsync(
- 'UPDATE users SET country = ?, city = ?, district = ? WHERE telegram_id = ?',
- [country, city, district, userId.toString()]
- );
-
- await db.runAsync('COMMIT');
-
- await this.bot.editMessageText(
- `✅ Location updated successfully!\n\nCountry: ${country}\nCity: ${city}\nDistrict: ${district}`,
- {
- chat_id: chatId,
- message_id: messageId,
- reply_markup: {
- inline_keyboard: [[
- { text: '« Back to Profile', callback_data: 'back_to_profile' }
- ]]
- }
- }
- );
- } catch (error) {
- await db.runAsync('ROLLBACK');
- console.error('Error in handleSetDistrict:', error);
- await this.bot.sendMessage(chatId, 'Error updating location. Please try again.');
- }
- }
-}
\ No newline at end of file
diff --git a/src/index.js b/src/index.js
index 825c87a..4951c5b 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,18 +1,16 @@
-import TelegramBot from 'node-telegram-bot-api';
-import config from './config/config.js';
-import UserHandler from './handlers/userHandler.js';
-import UserProductHandler from './handlers/userProductHandler.js';
-import UserWalletsHandler from './handlers/userWalletsHandler.js';
-import UserLocationHandler from './handlers/userLocationHandler.js';
-import AdminHandler from './handlers/adminHandler.js';
-import AdminUserHandler from './handlers/adminUserHandler.js';
-import AdminLocationHandler from './handlers/adminLocationHandler.js';
-import AdminProductHandler from './handlers/adminProductHandler.js';
+import adminUserHandler from './handlers/adminHandlers/adminUserHandler.js';
import ErrorHandler from './utils/errorHandler.js';
-import User from './models/User.js';
-import AdminUserLocationHandler from "./handlers/adminUserLocationHandler.js";
-import AdminDumpHandler from "./handlers/adminDumpHandler.js";
-import UserPurchaseHandler from "./handlers/userPurchaseHandler.js";
+import bot from "./context/bot.js";
+import userHandler from "./handlers/userHandlers/userHandler.js";
+import userPurchaseHandler from "./handlers/userHandlers/userPurchaseHandler.js";
+import userLocationHandler from "./handlers/userHandlers/userLocationHandler.js";
+import userProductHandler from "./handlers/userHandlers/userProductHandler.js";
+import userWalletsHandler from "./handlers/userHandlers/userWalletsHandler.js";
+import adminHandler from "./handlers/adminHandlers/adminHandler.js";
+import adminUserLocationHandler from "./handlers/adminHandlers/adminUserLocationHandler.js";
+import adminDumpHandler from "./handlers/adminHandlers/adminDumpHandler.js";
+import adminLocationHandler from "./handlers/adminHandlers/adminLocationHandler.js";
+import adminProductHandler from "./handlers/adminHandlers/adminProductHandler.js";
// Debug logging function
const logDebug = (action, functionName) => {
@@ -20,30 +18,6 @@ const logDebug = (action, functionName) => {
console.log(`[DEBUG] Calling Function: ${functionName}`);
};
-const initBot = () => {
- try {
- const bot = new TelegramBot(config.BOT_TOKEN, {polling: true});
- console.log('Bot initialized successfully');
- return bot;
- } catch (error) {
- console.error('Failed to initialize bot:', error);
- process.exit(1);
- }
-};
-
-const bot = initBot();
-const userHandler = new UserHandler(bot);
-const userProductHandler = new UserProductHandler(bot);
-const userWalletsHandler = new UserWalletsHandler(bot);
-const userLocationHandler = new UserLocationHandler(bot);
-const adminHandler = new AdminHandler(bot);
-const adminUserHandler = new AdminUserHandler(bot);
-const adminLocationHandler = new AdminLocationHandler(bot);
-const adminUserLocationHandler = new AdminUserLocationHandler(bot);
-const adminProductHandler = new AdminProductHandler(bot);
-const adminDumpHandler = new AdminDumpHandler(bot);
-const userPurchaseHandler = new UserPurchaseHandler(bot);
-
// Start command - Create user profile
bot.onText(/\/start/, async (msg) => {
logDebug('/start', 'handleStart');
@@ -119,6 +93,11 @@ bot.on('message', async (msg) => {
return;
}
+ // Check for category update input
+ if (await adminProductHandler.handleCategoryUpdate(msg)) {
+ return;
+ }
+
logDebug(msg.text, 'handleMessage');
switch (msg.text) {
diff --git a/src/models/User.js b/src/models/User.js
deleted file mode 100644
index 80a66db..0000000
--- a/src/models/User.js
+++ /dev/null
@@ -1,127 +0,0 @@
-import db from '../config/database.js';
-import Wallet from "./Wallet.js";
-
-export default class User {
- static async create(telegramId, username) {
- try {
- // First check if user exists
- const existingUser = await this.getById(telegramId);
- if (existingUser) {
- return existingUser.id;
- }
-
- // Begin transaction
- await db.runAsync('BEGIN TRANSACTION');
-
- // Create new user
- const result = await db.runAsync(
- 'INSERT INTO users (telegram_id, username) VALUES (?, ?)',
- [telegramId.toString(), username]
- );
-
- // Commit transaction
- await db.runAsync('COMMIT');
-
- return result.lastID;
- } catch (error) {
- // Rollback on error
- await db.runAsync('ROLLBACK');
- console.error('Error creating user:', error);
- throw error;
- }
- }
-
- static async getById(telegramId) {
- try {
- return await db.getAsync(
- 'SELECT * FROM users WHERE telegram_id = ?',
- [telegramId.toString()]
- );
- } catch (error) {
- console.error('Error getting user:', error);
- throw error;
- }
- }
-
- static async getUserStats(telegramId) {
- try {
- return await db.getAsync(`
- SELECT
- u.*,
- COUNT(DISTINCT p.id) as purchase_count,
- COALESCE(SUM(p.total_price), 0) as total_spent,
- COUNT(DISTINCT cw.id) as crypto_wallet_count,
- COUNT(DISTINCT cw2.id) as archived_wallet_count
- FROM users u
- LEFT JOIN purchases p ON u.id = p.user_id
- LEFT JOIN crypto_wallets cw ON u.id = cw.user_id AND cw.wallet_type NOT LIKE '%_%'
- LEFT JOIN crypto_wallets cw2 ON u.id = cw2.user_id AND cw2.wallet_type LIKE '%_%'
- WHERE u.telegram_id = ?
- GROUP BY u.id
- `, [telegramId.toString()]);
- } catch (error) {
- console.error('Error getting user stats:', error);
- throw error;
- }
- }
-
- static async updateUserStatus(telegramId, status) {
- // statuses
- // 0 - active
- // 1 - deleted
- // 2 - blocked
-
- try {
- await db.runAsync('BEGIN TRANSACTION');
-
- // Update user status
- await db.runAsync('UPDATE users SET status = ? WHERE telegram_id = ?', [status, telegramId.toString()]);
-
- // Commit transaction
- await db.runAsync('COMMIT');
- } catch (e) {
- await db.runAsync("ROLLBACK");
- console.error('Error deleting user:', error);
- throw error;
- }
- }
-
- static async delete(telegramId) {
- try {
- await db.runAsync('BEGIN TRANSACTION');
-
- // Delete user and his data
- await db.runAsync('DELETE FROM users WHERE telegram_id = ?', [telegramId.toString()]);
- await db.runAsync('DELETE FROM transactions WHERE user_id = ?', [telegramId.toString()]);
- await db.runAsync('DELETE FROM purchases WHERE user_id = ?', [telegramId.toString()]);
- await db.runAsync('DELETE FROM crypto_wallets WHERE user_id = ?', [telegramId.toString()]);
-
- // Commit transaction
- await db.runAsync('COMMIT');
- } catch (e) {
- await db.runAsync("ROLLBACK");
- console.error('Error deleting user:', error);
- throw error;
- }
- }
-
- static async recalculateBalance(telegramId) {
- const user = await User.getById(telegramId);
-
- if (!user) {
- return;
- }
-
- const archivedBalance = await Wallet.getArchivedWalletsBalance(user.id);
- const activeBalance = await Wallet.getActiveWalletsBalance(user.id);
-
- const purchases = await db.getAsync(
- `SELECT SUM(total_price) as total_sum FROM purchases WHERE user_id = ?`,
- [user.id]
- );
-
- const userTotalBalance = (activeBalance + archivedBalance) - (purchases?.total_sum || 0);
-
- await db.runAsync(`UPDATE users SET total_balance = ? WHERE id = ?`, [userTotalBalance, user.id]);
- }
-}
\ No newline at end of file
diff --git a/src/services/categoryService.js b/src/services/categoryService.js
new file mode 100644
index 0000000..bc6c803
--- /dev/null
+++ b/src/services/categoryService.js
@@ -0,0 +1,27 @@
+import db from "../config/database.js";
+
+class CategoryService {
+ static async getCategoriesByLocationId(locationId) {
+ return await db.allAsync(
+ 'SELECT id, name FROM categories WHERE location_id = ? ORDER BY name',
+ [locationId]
+ );
+ }
+
+ static async getSubcategoriesByCategoryId(categoryId) {
+ return await db.allAsync(
+ 'SELECT id, name FROM subcategories WHERE category_id = ? ORDER BY name',
+ [categoryId]
+ );
+ }
+
+ static async getCategoryById(categoryId) {
+ return await db.getAsync('SELECT id, name FROM categories WHERE id = ?', [categoryId]);
+ }
+
+ static async getSubcategoryById(subcategoryId) {
+ return await db.getAsync('SELECT id, name FROM subcategories WHERE id = ?', [subcategoryId]);
+ }
+}
+
+export default CategoryService;
\ No newline at end of file
diff --git a/src/services/locationService.js b/src/services/locationService.js
new file mode 100644
index 0000000..4c47adb
--- /dev/null
+++ b/src/services/locationService.js
@@ -0,0 +1,37 @@
+import db from "../config/database.js";
+
+class LocationService {
+ static async getCountries() {
+ return await db.allAsync('SELECT DISTINCT country FROM locations ORDER BY country');
+ }
+
+ static async getCitiesByCountry(country) {
+ return await db.allAsync(
+ 'SELECT DISTINCT city FROM locations WHERE country = ? ORDER BY city',
+ [country]
+ );
+ }
+
+ static async getDistrictsByCountryAndCity(country, city) {
+ return await db.allAsync(
+ 'SELECT district FROM locations WHERE country = ? AND city = ? ORDER BY district',
+ [country, city]
+ );
+ }
+
+ static async getLocation(country, city, district) {
+ return await db.getAsync(
+ 'SELECT id FROM locations WHERE country = ? AND city = ? AND district = ?',
+ [country, city, district]
+ );
+ }
+
+ static async getLocationById(locationId) {
+ return await db.getAsync(
+ 'SELECT country, city, district FROM locations WHERE id = ?',
+ [locationId]
+ );
+ }
+}
+
+export default LocationService;
\ No newline at end of file
diff --git a/src/services/productService.js b/src/services/productService.js
new file mode 100644
index 0000000..52f1669
--- /dev/null
+++ b/src/services/productService.js
@@ -0,0 +1,37 @@
+import db from "../config/database.js";
+
+class ProductService {
+ static async getProductById(productId) {
+ try {
+ return await db.getAsync(`SELECT * FROM products WHERE id = ?`, [productId]);
+ } catch (error) {
+ console.error('Error get product:', error);
+ throw error;
+ }
+ }
+
+ static async getDetailedProductById(productId) {
+ return await db.getAsync(
+ `SELECT p.*, c.name as category_name, s.name as subcategory_name
+ FROM products p
+ JOIN categories c ON p.category_id = c.id
+ JOIN subcategories s ON p.subcategory_id = s.id
+ WHERE p.id = ?`,
+ [productId]
+ );
+ }
+
+ static async getProductsByLocationAndCategory(locationId, categoryId, subcategoryId) {
+ return await db.allAsync(
+ `SELECT id, name, price, description, quantity_in_stock, photo_url
+ FROM products
+ WHERE location_id = ? AND category_id = ? AND subcategory_id = ?
+ AND quantity_in_stock > 0
+ ORDER BY name`,
+ [locationId, categoryId, subcategoryId]
+ );
+
+ }
+}
+
+export default ProductService
\ No newline at end of file
diff --git a/src/services/purchaseService.js b/src/services/purchaseService.js
new file mode 100644
index 0000000..1da4d3b
--- /dev/null
+++ b/src/services/purchaseService.js
@@ -0,0 +1,59 @@
+import db from "../config/database.js";
+
+class PurchaseService {
+ static async getPurchasesByUserId(userId, limit, offset) {
+ try {
+ return await db.allAsync(`
+ SELECT
+ p.*,
+ pr.name as product_name,
+ pr.description,
+ l.country,
+ l.city,
+ l.district
+ FROM purchases p
+ JOIN products pr ON p.product_id = pr.id
+ JOIN locations l ON pr.location_id = l.id
+ WHERE p.user_id = ?
+ ORDER BY p.purchase_date DESC
+ LIMIT ?
+ OFFSET ?
+ `, [userId, limit, offset]);
+
+ } catch (error) {
+ console.error('Error get purchases:', error);
+ throw error;
+ }
+ }
+
+ static async getPurchaseById(purchaseId) {
+ try {
+ return await db.getAsync(`
+ SELECT
+ p.*,
+ pr.name as product_name,
+ pr.description,
+ l.country,
+ l.city,
+ l.district
+ FROM purchases p
+ JOIN products pr ON p.product_id = pr.id
+ JOIN locations l ON pr.location_id = l.id
+ WHERE p.id = ?
+ `, [purchaseId]);
+ } catch (error) {
+ console.error('Error get purchase:', error);
+ throw error;
+ }
+
+ }
+
+ static async createPurchase(userId, productId, walletType, quantity, totalPrice) {
+ await db.runAsync(
+ 'INSERT INTO purchases (user_id, product_id, wallet_type, tx_hash, quantity, total_price) VALUES (?, ?, ?, ?, ?, ?)',
+ [userId, productId, walletType, "null", quantity, totalPrice]
+ );
+ }
+}
+
+export default PurchaseService
\ No newline at end of file
diff --git a/src/services/userService.js b/src/services/userService.js
new file mode 100644
index 0000000..322b59c
--- /dev/null
+++ b/src/services/userService.js
@@ -0,0 +1,140 @@
+import db from "../config/database.js";
+import Wallet from "../models/Wallet.js";
+
+class UserService {
+ static async getUserByUserId(userId) {
+ try {
+ return await db.getAsync(
+ 'SELECT * FROM users WHERE id = ?',
+ [String(userId)]
+ );
+ } catch (error) {
+ console.error('Error getting user:', error);
+ throw error;
+ }
+ }
+
+ static async getUserByTelegramId(telegramId) {
+ try {
+ return await db.getAsync(
+ 'SELECT * FROM users WHERE telegram_id = ?',
+ [String(telegramId)]
+ );
+ } catch (error) {
+ console.error('Error getting user:', error);
+ throw error;
+ }
+ }
+
+ static async getDetailedUserByTelegramId(telegramId) {
+ try {
+ return await db.getAsync(`
+ SELECT
+ u.*,
+ COUNT(DISTINCT p.id) as purchase_count,
+ COALESCE(SUM(p.total_price), 0) as total_spent,
+ COUNT(DISTINCT cw.id) as crypto_wallet_count,
+ COUNT(DISTINCT cw2.id) as archived_wallet_count
+ FROM users u
+ LEFT JOIN purchases p ON u.id = p.user_id
+ LEFT JOIN crypto_wallets cw ON u.id = cw.user_id AND cw.wallet_type NOT LIKE '%_%'
+ LEFT JOIN crypto_wallets cw2 ON u.id = cw2.user_id AND cw2.wallet_type LIKE '%_%'
+ WHERE u.telegram_id = ?
+ GROUP BY u.id
+ `, [telegramId.toString()]);
+ } catch (error) {
+ console.error('Error getting user stats:', error);
+ throw error;
+ }
+ }
+
+ static async createUser(userData) {
+ try {
+ const existingUser = await this.getUserByTelegramId(userData?.telegram_id);
+
+ if (existingUser) {
+ return existingUser.id;
+ }
+
+ const fields = Object.keys(userData);
+ const values = [];
+
+ for (const field of fields) {
+ values.push(userData[field]);
+ }
+
+ const marks = [];
+ for (let i = 0; i < fields.length; i++) {
+ marks.push("?");
+ }
+
+ const query = [
+ `INSERT INTO users (${fields.join(', ')})`,
+ `VALUES (${marks.join(', ')})`
+ ].join("");
+
+ await db.runAsync('BEGIN TRANSACTION');
+ const result = await db.runAsync(query, [values]);
+ await db.runAsync('COMMIT');
+ return result.lastID;
+ } catch (error) {
+ await db.runAsync('ROLLBACK');
+ console.error('Error creating user:', error);
+ throw error;
+ }
+ }
+
+ static async updateUser(userId, newUserData) {}
+
+ static async deleteUser() {}
+
+ static async recalculateUserBalanceByTelegramId(telegramId) {
+ const user = await this.getUserByTelegramId(telegramId);
+
+ if (!user) {
+ return;
+ }
+
+ const archivedBalance = await Wallet.getArchivedWalletsBalance(user.id);
+ const activeBalance = await Wallet.getActiveWalletsBalance(user.id);
+
+ const purchases = await db.getAsync(
+ `SELECT SUM(total_price) as total_sum FROM purchases WHERE user_id = ?`,
+ [user.id]
+ );
+
+ const userTotalBalance = (activeBalance + archivedBalance) - (purchases?.total_sum || 0);
+
+ await db.runAsync(`UPDATE users SET total_balance = ? WHERE id = ?`, [userTotalBalance, user.id]);
+ }
+
+ static async updateUserLocation(telegramId, country, city, district) {
+ await db.runAsync(
+ 'UPDATE users SET country = ?, city = ?, district = ? WHERE telegram_id = ?',
+ [country, city, district, telegramId.toString()]
+ );
+ }
+
+ static async updateUserStatus(telegramId, status) {
+ // statuses
+ // 0 - active
+ // 1 - deleted
+ // 2 - blocked
+
+ try {
+ await db.runAsync('BEGIN TRANSACTION');
+
+ // Update user status
+ await db.runAsync('UPDATE users SET status = ? WHERE telegram_id = ?', [status, telegramId.toString()]);
+
+ // Commit transaction
+ await db.runAsync('COMMIT');
+ } catch (e) {
+ await db.runAsync("ROLLBACK");
+ console.error('Error deleting user:', e);
+ throw e;
+ }
+ }
+}
+
+export default UserService;
\ No newline at end of file
diff --git a/src/services/walletService.js b/src/services/walletService.js
new file mode 100644
index 0000000..ba2f5be
--- /dev/null
+++ b/src/services/walletService.js
@@ -0,0 +1,3 @@
+class WalletService {
+
+}
\ No newline at end of file