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 71% rename from src/handlers/adminProductHandler.js rename to src/handlers/adminHandlers/adminProductHandler.js index 4851b63..51012d1 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,58 @@ 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}`} ]] } } ); } - async handleCategorySelection(callbackQuery) { + static async handleCategorySelection(callbackQuery) { + if (!this.isAdmin(callbackQuery.from.id)) { + return; + } + 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 +279,7 @@ export default class AdminProductHandler { ] }; - await this.bot.editMessageText( + await bot.editMessageText( `📦 Category: ${category.name}\nSelect or add subcategory:`, { chat_id: chatId, @@ -280,15 +289,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 +314,7 @@ export default class AdminProductHandler { [categoryId, msg.text] ); - await this.bot.sendMessage( + await bot.sendMessage( chatId, `✅ Subcategory "${msg.text}" added successfully!`, { @@ -313,26 +329,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 +366,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 +444,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 +478,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 +493,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 +504,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 +531,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 +553,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 +577,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 +600,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 +625,7 @@ export default class AdminProductHandler { await db.runAsync('COMMIT'); - await this.bot.sendMessage( + await bot.sendMessage( chatId, `✅ Successfully imported ${products.length} products!`, { @@ -612,21 +640,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 +670,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 +713,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 +738,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 +777,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 +806,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 +866,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 +874,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 +889,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 +920,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 +931,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 +969,7 @@ Coordinates: ${product.hidden_coordinates} ] }; - await this.bot.editMessageText( + await bot.editMessageText( `✅ Product has been successfully deleted.`, { chat_id: chatId, @@ -963,7 +979,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 61% rename from src/handlers/userHandler.js rename to src/handlers/userHandlers/userHandler.js index 1e190c1..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,26 +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 { - await User.recalculateBalance(userId); - 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; } @@ -50,7 +47,7 @@ export default class UserHandler { const text = ` 👤 *Your Profile* -📱 Telegram ID: \`${userId}\` +📱 Telegram ID: \`${telegramId}\` 📍 Location: ${locationText} 📊 Statistics: @@ -70,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: { @@ -99,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..84fcde5 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'); 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