diff --git a/corrupt-photo.jpg b/corrupt-photo.jpg new file mode 100644 index 0000000..df39d0c Binary files /dev/null and b/corrupt-photo.jpg differ 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/adminProductHandler.js b/src/handlers/adminProductHandler.js index cd8f236..65587f1 100644 --- a/src/handlers/adminProductHandler.js +++ b/src/handlers/adminProductHandler.js @@ -1,538 +1,630 @@ import db from '../config/database.js'; import config from '../config/config.js'; import fs from 'fs/promises'; +import User from "../models/User.js"; export default class AdminProductHandler { - constructor(bot) { - this.bot = bot; - this.userStates = new Map(); - } - - isAdmin(userId) { - return config.ADMIN_IDS.includes(userId.toString()); - } - - 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.'); - return; + constructor(bot) { + this.bot = bot; + this.userStates = new Map(); } - try { - const countries = await db.allAsync( - 'SELECT DISTINCT country FROM locations ORDER BY country' - ); + isAdmin(userId) { + return config.ADMIN_IDS.includes(userId.toString()); + } - if (countries.length === 0) { - await this.bot.sendMessage( - chatId, - 'No locations available. Please add locations first.', - { - reply_markup: { - inline_keyboard: [[ - { text: '๐ Manage Locations', callback_data: 'view_locations' } - ]] + 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.'); + return; + } + + try { + const countries = await db.allAsync( + 'SELECT DISTINCT country FROM locations ORDER BY country' + ); + + if (countries.length === 0) { + await this.bot.sendMessage( + chatId, + 'No locations available. Please add locations first.', + { + reply_markup: { + inline_keyboard: [[ + {text: '๐ Manage Locations', callback_data: 'view_locations'} + ]] + } + } + ); + return; + } + + const keyboard = { + inline_keyboard: countries.map(loc => [{ + text: loc.country, + callback_data: `prod_country_${loc.country}` + }]) + }; + + await this.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.'); + } + } + + async handleCountrySelection(callbackQuery) { + 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 keyboard = { + inline_keyboard: [ + ...cities.map(loc => [{ + text: loc.city, + callback_data: `prod_city_${country}_${loc.city}` + }]), + [{text: 'ยซ Back', callback_data: 'manage_products'}] + ] + }; + + await this.bot.editMessageText( + `๐ Select city in ${country}:`, + { + chat_id: chatId, + message_id: messageId, + reply_markup: keyboard + } + ); + } catch (error) { + console.error('Error in handleCountrySelection:', error); + await this.bot.sendMessage(chatId, 'Error loading cities. Please try again.'); + } + } + + async handleCitySelection(callbackQuery) { + 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 keyboard = { + inline_keyboard: [ + ...districts.map(loc => [{ + text: loc.district, + callback_data: `prod_district_${country}_${city}_${loc.district}` + }]), + [{text: 'ยซ Back', callback_data: `prod_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 handleCitySelection:', error); + await this.bot.sendMessage(chatId, 'Error loading districts. Please try again.'); + } + } + + async handleDistrictSelection(callbackQuery) { + const chatId = callbackQuery.message.chat.id; + const messageId = callbackQuery.message.message_id; + const [country, city, district] = callbackQuery.data.replace('prod_district_', '').split('_'); + + try { + const location = await db.getAsync( + 'SELECT id FROM locations WHERE country = ? AND city = ? AND district = ?', + [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 keyboard = { + inline_keyboard: [ + ...categories.map(cat => [{ + text: cat.name, + callback_data: `prod_category_${location.id}_${cat.id}` + }]), + [{text: 'โ Add Category', callback_data: `add_category_${location.id}`}], + [{text: 'ยซ Back', callback_data: `prod_city_${country}_${city}`}] + ] + }; + + await this.bot.editMessageText( + '๐ฆ Select or add category:', + { + chat_id: chatId, + message_id: messageId, + reply_markup: keyboard + } + ); + } catch (error) { + console.error('Error in handleDistrictSelection:', error); + await this.bot.sendMessage(chatId, 'Error loading categories. Please try again.'); + } + } + + async handleCategoryInput(msg) { + const chatId = msg.chat.id; + const state = this.userStates.get(chatId); + + if (!state || !state.action?.startsWith('add_category_')) return false; + + try { + const locationId = state.action.replace('add_category_', ''); + + await db.runAsync( + 'INSERT INTO categories (location_id, name) VALUES (?, ?)', + [locationId, msg.text] + ); + + const location = await db.getAsync( + 'SELECT country, city, district FROM locations WHERE id = ?', + [locationId] + ); + + await this.bot.sendMessage( + chatId, + `โ Category "${msg.text}" added successfully!`, + { + reply_markup: { + inline_keyboard: [[ + { + text: 'ยซ Back to Categories', + callback_data: `prod_district_${location.country}_${location.city}_${location.district}` + } + ]] + } + } + ); + + this.userStates.delete(chatId); + } catch (error) { + if (error.code === 'SQLITE_CONSTRAINT') { + await this.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.'); + } + } + + return true; + } + + async handleAddCategory(callbackQuery) { + const chatId = callbackQuery.message.chat.id; + const locationId = callbackQuery.data.replace('add_category_', ''); + + this.userStates.set(chatId, {action: `add_category_${locationId}`}); + + await this.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}`} + ]] + } } - } ); - return; - } - - const keyboard = { - inline_keyboard: countries.map(loc => [{ - text: loc.country, - callback_data: `prod_country_${loc.country}` - }]) - }; - - await this.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.'); - } - } - - async handleCountrySelection(callbackQuery) { - 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 keyboard = { - inline_keyboard: [ - ...cities.map(loc => [{ - text: loc.city, - callback_data: `prod_city_${country}_${loc.city}` - }]), - [{ text: 'ยซ Back', callback_data: 'manage_products' }] - ] - }; - - await this.bot.editMessageText( - `๐ Select city in ${country}:`, - { - chat_id: chatId, - message_id: messageId, - reply_markup: keyboard - } - ); - } catch (error) { - console.error('Error in handleCountrySelection:', error); - await this.bot.sendMessage(chatId, 'Error loading cities. Please try again.'); - } - } - - async handleCitySelection(callbackQuery) { - 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 keyboard = { - inline_keyboard: [ - ...districts.map(loc => [{ - text: loc.district, - callback_data: `prod_district_${country}_${city}_${loc.district}` - }]), - [{ text: 'ยซ Back', callback_data: `prod_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 handleCitySelection:', error); - await this.bot.sendMessage(chatId, 'Error loading districts. Please try again.'); - } - } - - async handleDistrictSelection(callbackQuery) { - const chatId = callbackQuery.message.chat.id; - const messageId = callbackQuery.message.message_id; - const [country, city, district] = callbackQuery.data.replace('prod_district_', '').split('_'); - - try { - const location = await db.getAsync( - 'SELECT id FROM locations WHERE country = ? AND city = ? AND district = ?', - [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 keyboard = { - inline_keyboard: [ - ...categories.map(cat => [{ - text: cat.name, - callback_data: `prod_category_${location.id}_${cat.id}` - }]), - [{ text: 'โ Add Category', callback_data: `add_category_${location.id}` }], - [{ text: 'ยซ Back', callback_data: `prod_city_${country}_${city}` }] - ] - }; - - await this.bot.editMessageText( - '๐ฆ Select or add category:', - { - chat_id: chatId, - message_id: messageId, - reply_markup: keyboard - } - ); - } catch (error) { - console.error('Error in handleDistrictSelection:', error); - await this.bot.sendMessage(chatId, 'Error loading categories. Please try again.'); - } - } - - async handleCategoryInput(msg) { - const chatId = msg.chat.id; - const state = this.userStates.get(chatId); - - if (!state || !state.action?.startsWith('add_category_')) return false; - - try { - const locationId = state.action.replace('add_category_', ''); - - await db.runAsync( - 'INSERT INTO categories (location_id, name) VALUES (?, ?)', - [locationId, msg.text] - ); - - const location = await db.getAsync( - 'SELECT country, city, district FROM locations WHERE id = ?', - [locationId] - ); - - await this.bot.sendMessage( - chatId, - `โ Category "${msg.text}" added successfully!`, - { - reply_markup: { - inline_keyboard: [[ - { text: 'ยซ Back to Categories', callback_data: `prod_district_${location.country}_${location.city}_${location.district}` } - ]] - } - } - ); - - this.userStates.delete(chatId); - } catch (error) { - if (error.code === 'SQLITE_CONSTRAINT') { - await this.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.'); - } } - return true; - } + async handleCategorySelection(callbackQuery) { + const chatId = callbackQuery.message.chat.id; + const messageId = callbackQuery.message.message_id; + const [locationId, categoryId] = callbackQuery.data.replace('prod_category_', '').split('_'); - async handleAddCategory(callbackQuery) { - const chatId = callbackQuery.message.chat.id; - const locationId = callbackQuery.data.replace('add_category_', ''); - - this.userStates.set(chatId, { action: `add_category_${locationId}` }); - - await this.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}` } - ]] + 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 keyboard = { + inline_keyboard: [ + ...subcategories.map(sub => [{ + text: sub.name, + callback_data: `prod_subcategory_${locationId}_${categoryId}_${sub.id}` + }]), + [{text: 'โ Add Subcategory', callback_data: `add_subcategory_${locationId}_${categoryId}`}], + [{text: 'โ๏ธ Edit Category', callback_data: `edit_category_${locationId}_${categoryId}`}], + [{ + text: 'ยซ Back', + callback_data: `prod_district_${location.country}_${location.city}_${location.district}` + }] + ] + }; + + await this.bot.editMessageText( + `๐ฆ Category: ${category.name}\nSelect or add subcategory:`, + { + chat_id: chatId, + message_id: messageId, + reply_markup: keyboard + } + ); + } catch (error) { + console.error('Error in handleCategorySelection:', error); + await this.bot.sendMessage(chatId, 'Error loading subcategories. Please try again.'); } - } - ); - } - - async handleCategorySelection(callbackQuery) { - const chatId = callbackQuery.message.chat.id; - const messageId = callbackQuery.message.message_id; - const [locationId, categoryId] = callbackQuery.data.replace('prod_category_', '').split('_'); - - try { - const subcategories = await db.allAsync( - 'SELECT id, name FROM subcategories WHERE category_id = ? ORDER BY name', - [categoryId] - ); - - const category = await db.getAsync('SELECT name FROM categories WHERE id = ?', [categoryId]); - const location = await db.getAsync('SELECT country, city, district FROM locations WHERE id = ?', [locationId]); - - const keyboard = { - inline_keyboard: [ - ...subcategories.map(sub => [{ - text: sub.name, - callback_data: `prod_subcategory_${locationId}_${categoryId}_${sub.id}` - }]), - [{ text: 'โ Add Subcategory', callback_data: `add_subcategory_${locationId}_${categoryId}` }], - [{ text: 'โ๏ธ Edit Category', callback_data: `edit_category_${locationId}_${categoryId}` }], - [{ text: 'ยซ Back', callback_data: `prod_district_${location.country}_${location.city}_${location.district}` }] - ] - }; - - await this.bot.editMessageText( - `๐ฆ Category: ${category.name}\nSelect or add subcategory:`, - { - chat_id: chatId, - message_id: messageId, - reply_markup: keyboard - } - ); - } catch (error) { - console.error('Error in handleCategorySelection:', error); - await this.bot.sendMessage(chatId, 'Error loading subcategories. Please try again.'); - } - } - - async handleSubcategoryInput(msg) { - const chatId = msg.chat.id; - const state = this.userStates.get(chatId); - - if (!state || !state.action?.startsWith('add_subcategory_')) return false; - - try { - const [locationId, categoryId] = state.action.replace('add_subcategory_', '').split('_'); - - await db.runAsync( - 'INSERT INTO subcategories (category_id, name) VALUES (?, ?)', - [categoryId, msg.text] - ); - - await this.bot.sendMessage( - chatId, - `โ Subcategory "${msg.text}" added successfully!`, - { - reply_markup: { - inline_keyboard: [[ - { text: 'ยซ Back to Subcategories', callback_data: `prod_category_${locationId}_${categoryId}` } - ]] - } - } - ); - - this.userStates.delete(chatId); - } catch (error) { - if (error.code === 'SQLITE_CONSTRAINT') { - await this.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.'); - } } - return true; - } + async handleSubcategoryInput(msg) { + const chatId = msg.chat.id; + const state = this.userStates.get(chatId); - async handleAddSubcategory(callbackQuery) { - const chatId = callbackQuery.message.chat.id; - const [locationId, categoryId] = callbackQuery.data.replace('add_subcategory_', '').split('_'); - - this.userStates.set(chatId, { action: `add_subcategory_${locationId}_${categoryId}` }); - - await this.bot.editMessageText( - 'Please enter the name for the new subcategory:', - { - chat_id: chatId, - message_id: callbackQuery.message.message_id, - reply_markup: { - inline_keyboard: [[ - { text: 'โ Cancel', callback_data: `prod_category_${locationId}_${categoryId}` } - ]] + if (!state || !state.action?.startsWith('add_subcategory_')) return false; + + try { + const [locationId, categoryId] = state.action.replace('add_subcategory_', '').split('_'); + + await db.runAsync( + 'INSERT INTO subcategories (category_id, name) VALUES (?, ?)', + [categoryId, msg.text] + ); + + await this.bot.sendMessage( + chatId, + `โ Subcategory "${msg.text}" added successfully!`, + { + reply_markup: { + inline_keyboard: [[ + { + text: 'ยซ Back to Subcategories', + callback_data: `prod_category_${locationId}_${categoryId}` + } + ]] + } + } + ); + + this.userStates.delete(chatId); + } catch (error) { + if (error.code === 'SQLITE_CONSTRAINT') { + await this.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.'); + } } - } - ); - } - async handleSubcategorySelection(callbackQuery) { - const chatId = callbackQuery.message.chat.id; - const messageId = callbackQuery.message.message_id; - const [locationId, categoryId, subcategoryId] = callbackQuery.data.replace('prod_subcategory_', '').split('_'); + return true; + } - try { - const products = await db.allAsync( - `SELECT id, name, price, quantity_in_stock + async handleAddSubcategory(callbackQuery) { + const chatId = callbackQuery.message.chat.id; + const [locationId, categoryId] = callbackQuery.data.replace('add_subcategory_', '').split('_'); + + this.userStates.set(chatId, {action: `add_subcategory_${locationId}_${categoryId}`}); + + await this.bot.editMessageText( + 'Please enter the name for the new subcategory:', + { + chat_id: chatId, + message_id: callbackQuery.message.message_id, + reply_markup: { + inline_keyboard: [[ + {text: 'โ Cancel', callback_data: `prod_category_${locationId}_${categoryId}`} + ]] + } + } + ); + } + + async viewProductsPage(locationId, categoryId, subcategoryId, page) { + try { + const limit = 10; + const offset = (page || 0) * limit; + + const previousPage = page > 0 ? page - 1 : 0; + const nextPage = page + 1; + + const products = await db.allAsync( + `SELECT id, name, price, quantity_in_stock FROM products WHERE location_id = ? AND category_id = ? AND subcategory_id = ? - ORDER BY name`, - [locationId, categoryId, subcategoryId] - ); + ORDER BY name + LIMIT ? + OFFSET ? + `, + [locationId, categoryId, subcategoryId, limit, offset] + ); - const subcategory = await db.getAsync('SELECT name FROM subcategories WHERE id = ?', [subcategoryId]); - const category = await db.getAsync('SELECT name FROM categories WHERE id = ?', [categoryId]); + if ((products.length === 0) && (page == 0)) { + return { + text: 'No products for this location', + markup: { + inline_keyboard: [ + [{text: 'ยซ Back', callback_data: `prod_category_${locationId}_${categoryId}`}] + ] + } + }; + } - const keyboard = { - inline_keyboard: [ - ...products.map(prod => [{ - text: `${prod.name} - $${prod.price} (${prod.quantity_in_stock} left)`, - callback_data: `view_product_${prod.id}` - }]), - [{ text: '๐ฅ Import Products', callback_data: `add_product_${locationId}_${categoryId}_${subcategoryId}` }], - [{ text: 'ยซ Back', callback_data: `prod_category_${locationId}_${categoryId}` }] - ] - }; + if ((products.length === 0) && (page > 0)) { + return await this.viewProductsPage(locationId, categoryId, subcategoryId, previousPage); + } - await this.bot.editMessageText( - `๐ฆ ${category.name} > ${subcategory.name}\nSelect product or import new ones:`, - { - chat_id: chatId, - message_id: messageId, - reply_markup: keyboard + const subcategory = await db.getAsync('SELECT name FROM subcategories WHERE id = ?', [subcategoryId]); + const category = await db.getAsync('SELECT name FROM categories WHERE id = ?', [categoryId]); + + const keyboard = { + inline_keyboard: [ + ...products.map(prod => [{ + text: `${prod.name} - $${prod.price} (${prod.quantity_in_stock} left)`, + callback_data: `view_product_${prod.id}` + }]), + [{ + text: '๐ฅ Import Products', + callback_data: `add_product_${locationId}_${categoryId}_${subcategoryId}` + }], + + ] + }; + + keyboard.inline_keyboard.push([ + {text: `ยซ`, callback_data: `list_products_${locationId}_${categoryId}_${subcategoryId}_${previousPage}`}, + {text: `ยป`, callback_data: `list_products_${locationId}_${categoryId}_${subcategoryId}_${nextPage}`}, + ]); + + keyboard.inline_keyboard.push([ + {text: 'ยซ Back', callback_data: `prod_category_${locationId}_${categoryId}`} + ]); + + return { + text: `๐ฆ ${category.name} > ${subcategory.name}\nSelect product or import new ones:`, + markup: keyboard + } + + } catch (error) { + console.error('Error in handleSubcategorySelection:', error); + return {text: 'Error loading products. Please try again.'}; } - ); - } catch (error) { - console.error('Error in handleSubcategorySelection:', error); - await this.bot.sendMessage(chatId, 'Error loading products. Please try again.'); } - } - async handleAddProduct(callbackQuery) { - const chatId = callbackQuery.message.chat.id; - const messageId = callbackQuery.message.message_id; - const [locationId, categoryId, subcategoryId] = callbackQuery.data.replace('add_product_', '').split('_'); + async handleSubcategorySelection(callbackQuery) { + const chatId = callbackQuery.message.chat.id; + const messageId = callbackQuery.message.message_id; + const [locationId, categoryId, subcategoryId] = callbackQuery.data.replace('prod_subcategory_', '').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]); + try { + const {text, markup} = await this.viewProductsPage(locationId, categoryId, subcategoryId, 0); - const sampleProducts = [ - { - name: "Sample Product 1", - price: 100, - description: "Product description", - private_data: "Hidden details about the product", - quantity_in_stock: 10, - photo_url: "https://example.com/photo.jpg", - hidden_photo_url: "https://example.com/hidden.jpg", - hidden_coordinates: "40.7128,-74.0060", - hidden_description: "Secret location details" + await this.bot.editMessageText( + text, + { + chat_id: chatId, + message_id: messageId, + reply_markup: markup + } + ); + } catch (error) { + console.error('Error in handleSubcategorySelection:', error); + await this.bot.sendMessage(chatId, 'Error loading products. Please try again.'); } - ]; - - 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, { - action: 'import_products', - locationId, - categoryId, - subcategoryId - }); - - await this.bot.editMessageText(message, { - chat_id: chatId, - message_id: messageId, - parse_mode: 'HTML', - reply_markup: { - inline_keyboard: [[ - { text: 'โ Cancel', callback_data: `prod_subcategory_${locationId}_${categoryId}_${subcategoryId}` } - ]] - } - }); - } catch (error) { - console.error('Error in handleAddProduct:', error); - await this.bot.sendMessage(chatId, 'Error preparing product import. Please try again.'); } - } - async handleProductImport(msg) { - const chatId = msg.chat.id; - const state = this.userStates.get(chatId); - - if (!state || state.action !== 'import_products') return false; - - try { - let products; - let jsonContent; - - // Handle file upload - if (msg.document) { - if (!msg.document.file_name.endsWith('.json')) { - await this.bot.sendMessage(chatId, 'Please upload a .json file.'); - return true; + async handleProductListPage(callbackQuery) { + if (!this.isAdmin(callbackQuery.from.id)) { + return; } - const file = await this.bot.getFile(msg.document.file_id); - const fileContent = await this.bot.downloadFile(file.file_id, '/tmp'); - jsonContent = await fs.readFile(fileContent, 'utf8'); - } else if (msg.text) { - jsonContent = msg.text; - } else { - await this.bot.sendMessage(chatId, 'Please send either a JSON file or JSON text.'); - return true; - } + const chatId = callbackQuery.message.chat.id; - try { - products = JSON.parse(jsonContent); - if (!Array.isArray(products)) { - throw new Error('Input must be an array of products'); + const [locationId, categoryId, subcategoryId, page] = callbackQuery.data.replace('list_products_', '').split("_"); + + try { + const {text, markup} = await this.viewProductsPage(locationId, categoryId, subcategoryId, parseInt(page)); + await this.bot.editMessageText(text, { + chat_id: chatId, + message_id: callbackQuery.message.message_id, + reply_markup: markup, + parse_mode: 'HTML' + }); + } catch (e) { + return; } - } catch (e) { - await this.bot.sendMessage(chatId, 'Invalid JSON format. Please check the format and try again.'); - return true; - } + } - await db.runAsync('BEGIN TRANSACTION'); + async handleAddProduct(callbackQuery) { + const chatId = callbackQuery.message.chat.id; + const messageId = callbackQuery.message.message_id; + const [locationId, categoryId, subcategoryId] = callbackQuery.data.replace('add_product_', '').split('_'); - for (const product of products) { - await db.runAsync( - `INSERT INTO products ( + 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", + price: 100, + description: "Product description", + private_data: "Hidden details about the product", + quantity_in_stock: 10, + photo_url: "https://example.com/photo.jpg", + hidden_photo_url: "https://example.com/hidden.jpg", + hidden_coordinates: "40.7128,-74.0060", + hidden_description: "Secret location details" + } + ]; + + 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, { + action: 'import_products', + locationId, + categoryId, + subcategoryId + }); + + await this.bot.editMessageText(message, { + chat_id: chatId, + message_id: messageId, + parse_mode: 'HTML', + reply_markup: { + inline_keyboard: [[ + { + text: 'โ Cancel', + callback_data: `prod_subcategory_${locationId}_${categoryId}_${subcategoryId}` + } + ]] + } + }); + } catch (error) { + console.error('Error in handleAddProduct:', error); + await this.bot.sendMessage(chatId, 'Error preparing product import. Please try again.'); + } + } + + async handleProductImport(msg) { + const chatId = msg.chat.id; + const state = this.userStates.get(chatId); + + if (!state || state.action !== 'import_products') return false; + + try { + let products; + let jsonContent; + + // Handle file upload + if (msg.document) { + if (!msg.document.file_name.endsWith('.json')) { + await this.bot.sendMessage(chatId, 'Please upload a .json file.'); + return true; + } + + const file = await this.bot.getFile(msg.document.file_id); + + const fileContent = await this.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.'); + return true; + } + + try { + products = JSON.parse(jsonContent); + if (!Array.isArray(products)) { + 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.'); + return true; + } + + await db.runAsync('BEGIN TRANSACTION'); + + for (const product of products) { + await db.runAsync( + `INSERT INTO products ( location_id, category_id, subcategory_id, name, price, description, private_data, quantity_in_stock, photo_url, hidden_photo_url, hidden_coordinates, hidden_description ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, - [ - 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.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 + ] + ); + } - await db.runAsync('COMMIT'); + await db.runAsync('COMMIT'); - await this.bot.sendMessage( - chatId, - `โ Successfully imported ${products.length} products!`, - { - reply_markup: { - inline_keyboard: [[ - { text: 'ยซ Back to Products', callback_data: `prod_subcategory_${state.locationId}_${state.categoryId}_${state.subcategoryId}` } - ]] - } + await this.bot.sendMessage( + chatId, + `โ Successfully imported ${products.length} products!`, + { + reply_markup: { + inline_keyboard: [[ + { + text: 'ยซ Back to Products', + callback_data: `prod_subcategory_${state.locationId}_${state.categoryId}_${state.subcategoryId}` + } + ]] + } + } + ); + + this.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 db.runAsync('ROLLBACK'); } - ); - this.userStates.delete(chatId); - } catch (error) { - await db.runAsync('ROLLBACK'); - console.error('Error importing products:', error); - await this.bot.sendMessage(chatId, 'Error importing products. Please check the data and try again.'); + return true; } - return true; - } + async handleViewProduct(callbackQuery) { + const chatId = callbackQuery.message.chat.id; + const messageId = callbackQuery.message.message_id; + const productId = callbackQuery.data.replace('view_product_', ''); - async handleViewProduct(callbackQuery) { - 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, + 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] - ); + [productId] + ); - if (!product) { - throw new Error('Product not found'); - } + if (!product) { + throw new Error('Product not found'); + } - const message = ` + const message = ` ๐ฆ Product Details: Name: ${product.name} @@ -549,32 +641,143 @@ Hidden Location: ${product.hidden_description} Coordinates: ${product.hidden_coordinates} `; - const keyboard = { - inline_keyboard: [ - [ - { text: 'โ๏ธ Edit', callback_data: `edit_product_${productId}` }, - { text: 'โ Delete', callback_data: `delete_product_${productId}` } - ], - [{ text: 'ยซ Back', callback_data: `prod_subcategory_${product.location_id}_${product.category_id}_${product.subcategory_id}` }] - ] - }; + const keyboard = { + inline_keyboard: [ + [ + {text: 'โ๏ธ Edit', callback_data: `edit_product_${productId}`}, + {text: 'โ Delete', callback_data: `delete_product_${productId}`} + ], + [{ + text: 'ยซ Back', + callback_data: `prod_subcategory_${product.location_id}_${product.category_id}_${product.subcategory_id}` + }] + ] + }; - // Send product photos - if (product.photo_url) { - await this.bot.sendPhoto(chatId, product.photo_url, { caption: 'Public photo' }); - } - if (product.hidden_photo_url) { - await this.bot.sendPhoto(chatId, product.hidden_photo_url, { caption: 'Hidden photo' }); - } + // Send product photos + if (product.photo_url) { + try { + await this.bot.sendPhoto(chatId, product.photo_url, {caption: 'Public photo'}); + } catch (e) { + await this.bot.sendPhoto(chatId, "./corrupt-photo.jpg", {caption: 'Public photo'}) + } + } + if (product.hidden_photo_url) { + try { + await this.bot.sendPhoto(chatId, product.hidden_photo_url, {caption: 'Hidden photo'}); + } catch (e) { + await this.bot.sendPhoto(chatId, "./corrupt-photo.jpg", {caption: 'Hidden photo'}) + } + } - await this.bot.editMessageText(message, { - chat_id: chatId, - message_id: messageId, - reply_markup: keyboard - }); - } catch (error) { - console.error('Error in handleViewProduct:', error); - await this.bot.sendMessage(chatId, 'Error loading product details. Please try again.'); + await this.bot.deleteMessage(chatId, messageId); + await this.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.'); + } } - } + + 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] + ); + + if (!product) { + throw new Error('Product not found'); + } + + const keyboard = { + inline_keyboard: [ + [ + {text: 'โ Confirm Delete', callback_data: `confirm_delete_product_${productId}`}, + {text: 'โ Cancel', callback_data: `prod_subcategory_${product.location_id}_${product.category_id}_${product.subcategory_id}`} + ] + ] + }; + + await this.bot.editMessageText( + `โ ๏ธ Are you sure you want to delete product\n\nThis action cannot be undone!`, + { + chat_id: chatId, + message_id: callbackQuery.message.message_id, + reply_markup: keyboard, + parse_mode: 'HTML' + } + ); + } catch (error) { + console.error('Error in handleDeleteUser:', error); + await this.bot.sendMessage(chatId, 'Error processing delete request. Please try again.'); + } + } + + 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] + ); + + 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; + } + + const keyboard = { + inline_keyboard: [ + [{ + text: 'ยซ Back', + callback_data: `prod_subcategory_${product.location_id}_${product.category_id}_${product.subcategory_id}` + }] + ] + }; + + await this.bot.editMessageText( + `โ Product has been successfully deleted.`, + { + chat_id: chatId, + message_id: callbackQuery.message.message_id, + reply_markup: keyboard + } + ); + } catch (error) { + console.error('Error in handleConfirmDelete:', error); + await this.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/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/handlers/userHandler.js b/src/handlers/userHandler.js index 6aa4b49..3dcac62 100644 --- a/src/handlers/userHandler.js +++ b/src/handlers/userHandler.js @@ -1,28 +1,52 @@ -import db from '../config/database.js'; import User from '../models/User.js'; +import config from "../config/config.js"; export default class UserHandler { - constructor(bot) { - this.bot = bot; - } + constructor(bot) { + this.bot = bot; + } - async showProfile(msg) { - const chatId = msg.chat.id; - const userId = msg.from.id; + async canUseBot(msg) { + const userId = msg.from.id; + const user = await User.getById(userId); - try { - const userStats = await User.getUserStats(userId); + const keyboard = { + inline_keyboard: [ + [{text: "Contact support", url: config.SUPPORT_LINK}] + ] + }; - if (!userStats) { - await this.bot.sendMessage(chatId, 'Profile not found. Please use /start to create one.'); - return; - } + switch (user?.status) { + case 0: + return true; + case 1: + await this.bot.sendMessage(userId, 'โ ๏ธ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}); + return false; + default: + return true + } + } - const locationText = userStats.country && userStats.city && userStats.district - ? `${userStats.country}, ${userStats.city}, ${userStats.district}` - : 'Not set'; + async showProfile(msg) { + const chatId = msg.chat.id; + const userId = msg.from.id; - const text = ` + try { + const userStats = await User.getUserStats(userId); + + if (!userStats) { + await this.bot.sendMessage(chatId, 'Profile not found. Please use /start to create one.'); + return; + } + + const locationText = userStats.country && userStats.city && userStats.district + ? `${userStats.country}, ${userStats.city}, ${userStats.district}` + : 'Not set'; + + const text = ` ๐ค *Your Profile* ๐ฑ Telegram ID: \`${userId}\` @@ -37,58 +61,58 @@ export default class UserHandler { ๐ Member since: ${new Date(userStats.created_at).toLocaleDateString()} `; - const keyboard = { - inline_keyboard: [ - [{ text: '๐ Set Location', callback_data: 'set_location' }], - [{ text: 'โ Delete Account', callback_data: 'delete_account' }] - ] - }; + const keyboard = { + inline_keyboard: [ + [{text: '๐ Set Location', callback_data: 'set_location'}], + [{text: 'โ Delete Account', callback_data: 'delete_account'}] + ] + }; - await this.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.'); - } - } - - async handleStart(msg) { - const chatId = msg.chat.id; - const userId = msg.from.id; - const username = msg.chat.username; - - try { - // Create user profile - await User.create(userId, username); - - const keyboard = { - reply_markup: { - keyboard: [ - ['๐ฆ Products', '๐ค Profile'], - ['๐ Purchases', '๐ฐ Wallets'] - ], - resize_keyboard: true + await this.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 this.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.'); } - } - 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); - } + async handleStart(msg) { + const chatId = msg.chat.id; + const userId = msg.from.id; + const username = msg.chat.username; + + try { + // Create user profile + await User.create(userId, username); + + const keyboard = { + reply_markup: { + keyboard: [ + ['๐ฆ Products', '๐ค Profile'], + ['๐ Purchases', '๐ฐ Wallets'] + ], + resize_keyboard: true + } + }; + + await this.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.'); + } + } + + 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); + } } \ No newline at end of file diff --git a/src/index.js b/src/index.js index fde65c1..72c16ef 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) { @@ -62,7 +69,15 @@ bot.onText(/\/admin/, async (msg) => { // Handle user menu buttons bot.on('message', async (msg) => { - if (!msg.text) return; + if (msg.text && msg.text.toLowerCase() === '/start') { + return; + } + + const canUse = await userHandler.canUseBot(msg); + + if (!canUse) { + return; + } try { // Check for admin location input @@ -126,6 +141,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 +233,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') { @@ -247,12 +269,21 @@ bot.on('callback_query', async (callbackQuery) => { } else if (action.startsWith('prod_subcategory_')) { logDebug(action, 'handleSubcategorySelection'); await adminProductHandler.handleSubcategorySelection(callbackQuery); + } else if (action.startsWith('list_products_')) { + logDebug(action, 'handleSubcategorySelection'); + await adminProductHandler.handleProductListPage(callbackQuery); } else if (action.startsWith('add_product_')) { logDebug(action, 'handleAddProduct'); await adminProductHandler.handleAddProduct(callbackQuery); } else if (action.startsWith('view_product_')) { logDebug(action, 'handleViewProduct'); await adminProductHandler.handleViewProduct(callbackQuery); + } else if (action.startsWith('delete_product_')) { + logDebug(action, 'handleViewProduct'); + await adminProductHandler.handleProductDelete(callbackQuery); + } else if (action.startsWith('confirm_delete_product_')) { + logDebug(action, 'handleConfirmDelete'); + await adminProductHandler.handleConfirmDelete(callbackQuery); } // Admin user management else if (action.startsWith('view_user_')) { @@ -264,9 +295,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