From f504d5fb7b9b49f4861f15bc83f50a9020764c31 Mon Sep 17 00:00:00 2001 From: Artyom Ashirov <1323ED5@gmail.com> Date: Fri, 15 Nov 2024 00:15:13 +0300 Subject: [PATCH 1/4] Back to admin menu fix --- src/handlers/adminHandler.js | 79 ++++++++++++++++++++++++------------ src/index.js | 3 ++ 2 files changed, 55 insertions(+), 27 deletions(-) diff --git a/src/handlers/adminHandler.js b/src/handlers/adminHandler.js index 8573c17..7a2e037 100644 --- a/src/handlers/adminHandler.js +++ b/src/handlers/adminHandler.js @@ -1,35 +1,60 @@ -import db from '../config/database.js'; import config from '../config/config.js'; export default class AdminHandler { - constructor(bot) { - this.bot = bot; - } - - isAdmin(userId) { - return config.ADMIN_IDS.includes(userId.toString()); - } - - async handleAdminCommand(msg) { - const chatId = msg.chat.id; - - if (!this.isAdmin(msg.from.id)) { - await this.bot.sendMessage(chatId, 'Unauthorized access.'); - return; + constructor(bot) { + this.bot = bot; } - const keyboard = { - reply_markup: { - keyboard: [ - ['👥 Manage Users', '📦 Manage Products'], - ['💰 Manage Wallets', '📍 Manage Locations'], - ['💾 Database Backup'] - ], - resize_keyboard: true - } - }; + isAdmin(userId) { + return config.ADMIN_IDS.includes(userId.toString()); + } - await this.bot.sendMessage(chatId, 'Admin Panel:', keyboard); - } + async handleAdminCommand(msg) { + const chatId = msg.chat.id; + if (!this.isAdmin(msg.from.id)) { + await this.bot.sendMessage(chatId, 'Unauthorized access.'); + return; + } + + const keyboard = { + reply_markup: { + keyboard: [ + ['👥 Manage Users', '📦 Manage Products'], + ['💰 Manage Wallets', '📍 Manage Locations'], + ['💾 Database Backup'] + ], + resize_keyboard: true + } + }; + + await this.bot.sendMessage(chatId, 'Admin Panel:', keyboard); + } + + async handleAdminQueryCommand(callbackQuery) { + if (!this.isAdmin(callbackQuery.from.id)) return; + + const chatId = callbackQuery.message.chat.id; + const messageId = callbackQuery.message.message_id; + + const keyboard = { + reply_markup: { + keyboard: [ + ['👥 Manage Users', '📦 Manage Products'], + ['💰 Manage Wallets', '📍 Manage Locations'], + ['💾 Database Backup'] + ], + resize_keyboard: true + } + }; + + await this.bot.editMessageText( + `You we're returned to the admin menu`, + { + chat_id: chatId, + message_id: messageId, + reply_markup: keyboard + } + ); + } } \ No newline at end of file diff --git a/src/index.js b/src/index.js index e88cb4c..e14cada 100644 --- a/src/index.js +++ b/src/index.js @@ -214,7 +214,10 @@ bot.on('callback_query', async (callbackQuery) => { } else if (action.startsWith('confirm_delete_')) { logDebug(action, 'handleConfirmDelete'); await adminLocationHandler.handleConfirmDelete(callbackQuery); + } else if (action === 'admin_menu') { + await adminHandler.handleAdminQueryCommand(callbackQuery); } + // Admin product management else if (action === 'manage_products') { logDebug(action, 'handleProductManagement'); From ebad9da43988424130582260e47ded4b02916fd6 Mon Sep 17 00:00:00 2001 From: Artyom Ashirov <1323ED5@gmail.com> Date: Fri, 15 Nov 2024 00:21:34 +0300 Subject: [PATCH 2/4] Another back to admin fix --- src/handlers/adminHandler.js | 27 ------------------------- src/handlers/adminLocationHandler.js | 30 ++++++++++++++++++++++++++++ src/index.js | 3 ++- 3 files changed, 32 insertions(+), 28 deletions(-) diff --git a/src/handlers/adminHandler.js b/src/handlers/adminHandler.js index 7a2e037..ef86a95 100644 --- a/src/handlers/adminHandler.js +++ b/src/handlers/adminHandler.js @@ -30,31 +30,4 @@ export default class AdminHandler { await this.bot.sendMessage(chatId, 'Admin Panel:', keyboard); } - - async handleAdminQueryCommand(callbackQuery) { - if (!this.isAdmin(callbackQuery.from.id)) return; - - const chatId = callbackQuery.message.chat.id; - const messageId = callbackQuery.message.message_id; - - const keyboard = { - reply_markup: { - keyboard: [ - ['👥 Manage Users', '📦 Manage Products'], - ['💰 Manage Wallets', '📍 Manage Locations'], - ['💾 Database Backup'] - ], - resize_keyboard: true - } - }; - - await this.bot.editMessageText( - `You we're returned to the admin menu`, - { - chat_id: chatId, - message_id: messageId, - reply_markup: keyboard - } - ); - } } \ No newline at end of file diff --git a/src/handlers/adminLocationHandler.js b/src/handlers/adminLocationHandler.js index 57f1fc3..3c20209 100644 --- a/src/handlers/adminLocationHandler.js +++ b/src/handlers/adminLocationHandler.js @@ -278,4 +278,34 @@ export default class AdminLocationHandler { ); } } + + + async backToMenu(callbackQuery) { + if (!this.isAdmin(callbackQuery.from.id)) return; + + const chatId = callbackQuery.message.chat.id; + const messageId = callbackQuery.message.message_id; + + const keyboard = { + reply_markup: { + keyboard: [ + ['👥 Manage Users', '📦 Manage Products'], + ['💰 Manage Wallets', '📍 Manage Locations'], + ['💾 Database Backup'] + ], + resize_keyboard: true + } + }; + + await this.bot.editMessageText( + `You we're returned to the admin menu`, + { + chat_id: chatId, + message_id: messageId, + reply_markup: keyboard + } + ); + + this.userStates.delete(chatId); + } } \ No newline at end of file diff --git a/src/index.js b/src/index.js index e14cada..fde65c1 100644 --- a/src/index.js +++ b/src/index.js @@ -215,7 +215,8 @@ bot.on('callback_query', async (callbackQuery) => { logDebug(action, 'handleConfirmDelete'); await adminLocationHandler.handleConfirmDelete(callbackQuery); } else if (action === 'admin_menu') { - await adminHandler.handleAdminQueryCommand(callbackQuery); + logDebug(action, 'backToMenu'); + await adminLocationHandler.backToMenu(callbackQuery); } // Admin product management From b45f7daa6f447c48f3e33ab527ee03d73262d5f3 Mon Sep 17 00:00:00 2001 From: Artyom Ashirov <1323ED5@gmail.com> Date: Fri, 15 Nov 2024 00:29:42 +0300 Subject: [PATCH 3/4] add location state reset --- src/handlers/adminLocationHandler.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/handlers/adminLocationHandler.js b/src/handlers/adminLocationHandler.js index 3c20209..e23233b 100644 --- a/src/handlers/adminLocationHandler.js +++ b/src/handlers/adminLocationHandler.js @@ -125,6 +125,8 @@ export default class AdminLocationHandler { return; } + this.userStates.delete(chatId); + try { const locations = await db.allAsync(` SELECT l.*, @@ -279,7 +281,6 @@ export default class AdminLocationHandler { } } - async backToMenu(callbackQuery) { if (!this.isAdmin(callbackQuery.from.id)) return; From 373e8e25677dee659a6d481af83d4a6876161ac1 Mon Sep 17 00:00:00 2001 From: Artyom Ashirov <1323ED5@gmail.com> Date: Fri, 15 Nov 2024 02:26:13 +0300 Subject: [PATCH 4/4] user deletion/blocking --- src/config/database.js | 1 + src/handlers/adminLocationHandler.js | 4 +- src/handlers/adminUserHandler.js | 74 ++++++++++++++++- src/index.js | 32 ++++++- src/models/User.js | 120 ++++++++++++++++++--------- 5 files changed, 187 insertions(+), 44 deletions(-) diff --git a/src/config/database.js b/src/config/database.js index e7a2e22..33c0ff6 100644 --- a/src/config/database.js +++ b/src/config/database.js @@ -94,6 +94,7 @@ const initDb = async () => { country TEXT, city TEXT, district TEXT, + status INTEGER DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) `); diff --git a/src/handlers/adminLocationHandler.js b/src/handlers/adminLocationHandler.js index e23233b..7c05143 100644 --- a/src/handlers/adminLocationHandler.js +++ b/src/handlers/adminLocationHandler.js @@ -215,7 +215,7 @@ export default class AdminLocationHandler { const keyboard = { inline_keyboard: locations.map(loc => [{ text: `${loc.country} > ${loc.city} > ${loc.district} (P:${loc.product_count} C:${loc.category_count})`, - callback_data: `confirm_delete_${loc.country}_${loc.city}_${loc.district}` + callback_data: `confirm_delete_location_${loc.country}_${loc.city}_${loc.district}` }]) }; @@ -239,7 +239,7 @@ export default class AdminLocationHandler { async handleConfirmDelete(callbackQuery) { const chatId = callbackQuery.message.chat.id; const [country, city, district] = callbackQuery.data - .replace('confirm_delete_', '') + .replace('confirm_delete_location_', '') .split('_'); try { diff --git a/src/handlers/adminUserHandler.js b/src/handlers/adminUserHandler.js index 25717ef..7ee340d 100644 --- a/src/handlers/adminUserHandler.js +++ b/src/handlers/adminUserHandler.js @@ -265,7 +265,7 @@ ${purchases.map(p => ` • ${p.product_name} x${p.quantity} - $${p.total_price} const chatId = callbackQuery.message.chat.id; try { - await User.delete(userId); + await User.updateUserStatus(userId, 1); const keyboard = { inline_keyboard: [ @@ -273,6 +273,12 @@ ${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'); + } catch (e) { + // ignore if we can't notify user + } + await this.bot.editMessageText( `✅ User ${userId} has been successfully deleted.`, { @@ -287,6 +293,72 @@ ${purchases.map(p => ` • ${p.product_name} x${p.quantity} - $${p.total_price} } } + async handleBlockUser(callbackQuery) { + if (!this.isAdmin(callbackQuery.from.id)) return; + + const userId = 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}`} + ] + ] + }; + + await this.bot.editMessageText( + `⚠️ Are you sure you want to block user ${userId}?`, + { + chat_id: chatId, + message_id: callbackQuery.message.message_id, + reply_markup: keyboard, + parse_mode: 'HTML' + } + ); + } catch (error) { + console.error('Error in handleBlockUser:', error); + await this.bot.sendMessage(chatId, 'Error processing block request. Please try again.'); + } + } + + async handleConfirmBlock(callbackQuery) { + if (!this.isAdmin(callbackQuery.from.id)) return; + + const userId = callbackQuery.data.replace('confirm_block_user_', ''); + const chatId = callbackQuery.message.chat.id; + + try { + await User.updateUserStatus(userId, 2); + + const keyboard = { + inline_keyboard: [ + [{text: '« Back to User List', callback_data: 'admin_users'}] + ] + }; + + try { + await this.bot.sendMessage(userId, '⚠️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.`, + { + chat_id: chatId, + message_id: callbackQuery.message.message_id, + reply_markup: keyboard + } + ); + } catch (error) { + console.error('Error in handleConfirmBlock:', error); + await this.bot.sendMessage(chatId, 'Error blocking user. Please try again.'); + } + } + async handleEditUserBalance(callbackQuery) { if (!this.isAdmin(callbackQuery.from.id)) return; diff --git a/src/index.js b/src/index.js index fde65c1..1f832eb 100644 --- a/src/index.js +++ b/src/index.js @@ -43,6 +43,13 @@ const adminProductHandler = new AdminProductHandler(bot); // Start command - Create user profile bot.onText(/\/start/, async (msg) => { logDebug('/start', 'handleStart'); + + const canUse = await userHandler.canUseBot(msg); + + if (!canUse) { + return; + } + try { await userHandler.handleStart(msg); } catch (error) { @@ -64,6 +71,16 @@ bot.onText(/\/admin/, async (msg) => { bot.on('message', async (msg) => { if (!msg.text) return; + if (msg.text.toLowerCase() === '/start') { + return; + } + + const canUse = await userHandler.canUseBot(msg); + + if (!canUse) { + return; + } + try { // Check for admin location input if (await adminLocationHandler.handleLocationInput(msg)) { @@ -126,6 +143,13 @@ bot.on('callback_query', async (callbackQuery) => { const action = callbackQuery.data; const msg = callbackQuery.message; + const canUse = await userHandler.canUseBot(callbackQuery); + + if (!canUse) { + await bot.answerCallbackQuery(callbackQuery.id); + return; + } + try { // Profile and location management if (action === 'set_location') { @@ -211,7 +235,7 @@ bot.on('callback_query', async (callbackQuery) => { } else if (action === 'delete_location') { logDebug(action, 'handleDeleteLocation'); await adminLocationHandler.handleDeleteLocation(callbackQuery); - } else if (action.startsWith('confirm_delete_')) { + } else if (action.startsWith('confirm_delete_location_')) { logDebug(action, 'handleConfirmDelete'); await adminLocationHandler.handleConfirmDelete(callbackQuery); } else if (action === 'admin_menu') { @@ -264,9 +288,15 @@ bot.on('callback_query', async (callbackQuery) => { } else if (action.startsWith('delete_user_')) { logDebug(action, 'handleDeleteUser'); await adminUserHandler.handleDeleteUser(callbackQuery); + } else if (action.startsWith('block_user_')) { + logDebug(action, 'handleDeleteUser'); + await adminUserHandler.handleBlockUser(callbackQuery); } else if (action.startsWith('confirm_delete_user_')) { logDebug(action, 'handleConfirmDelete'); await adminUserHandler.handleConfirmDelete(callbackQuery); + } else if (action.startsWith('confirm_block_user_')) { + logDebug(action, 'handleConfirmBlock'); + await adminUserHandler.handleConfirmBlock(callbackQuery); } else if (action.startsWith('edit_user_balance_')) { logDebug(action, 'handleEditUserBalance'); await adminUserHandler.handleEditUserBalance(callbackQuery); diff --git a/src/models/User.js b/src/models/User.js index 9e1de17..75e3bf7 100644 --- a/src/models/User.js +++ b/src/models/User.js @@ -1,50 +1,50 @@ import db from '../config/database.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; - } + 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'); + // 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] - ); + // Create new user + const result = await db.runAsync( + 'INSERT INTO users (telegram_id, username) VALUES (?, ?)', + [telegramId.toString(), username] + ); - // Commit transaction - await db.runAsync('COMMIT'); + // 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; + 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 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(` + static async getUserStats(telegramId) { + try { + return await db.getAsync(` SELECT u.*, COUNT(DISTINCT p.id) as purchase_count, @@ -58,9 +58,49 @@ export default class User { WHERE u.telegram_id = ? GROUP BY u.id `, [telegramId.toString()]); - } catch (error) { - console.error('Error getting user stats:', error); - throw error; + } 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; + } } - } } \ No newline at end of file