diff --git a/src/handlers/adminHandlers/adminProductHandler.js b/src/handlers/adminHandlers/adminProductHandler.js deleted file mode 100644 index 9ad79f9..0000000 --- a/src/handlers/adminHandlers/adminProductHandler.js +++ /dev/null @@ -1,1093 +0,0 @@ -import db from '../../config/database.js'; -import { isAdmin } from '../../middleware/auth.js'; -import fs from 'fs/promises'; -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"; -import Validators from '../../utils/validators.js'; - -export default class AdminProductHandler { - - static async handleProductManagement(msg) { - const chatId = msg.chat?.id || msg.message?.chat.id; - - if (!isAdmin(msg.from?.id || msg.message?.from.id)) { - await bot.sendMessage(chatId, 'Unauthorized access.'); - return; - } - - try { - const countries = await LocationService.getCountries() - - if (countries.length === 0) { - await 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 bot.sendMessage( - chatId, - 'π Select country to manage products:', - {reply_markup: keyboard} - ); - } catch (error) { - console.error('Error in handleProductManagement:', error); - await bot.sendMessage(chatId, 'Error loading locations. Please try again.'); - } - } - - static async handleCountrySelection(callbackQuery) { - if (!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 LocationService.getCitiesByCountry(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 bot.editMessageText( - `π Select city in ${country}:`, - { - chat_id: chatId, - message_id: messageId, - reply_markup: keyboard - } - ); - } catch (error) { - console.error('Error in handleCountrySelection:', error); - await bot.sendMessage(chatId, 'Error loading cities. Please try again.'); - } - } - - static async handleCitySelection(callbackQuery) { - if (!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 LocationService.getDistrictsByCountryAndCity(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 bot.editMessageText( - `π Select district in ${city}:`, - { - chat_id: chatId, - message_id: messageId, - reply_markup: keyboard - } - ); - } catch (error) { - console.error('Error in handleCitySelection:', error); - await bot.sendMessage(chatId, 'Error loading districts. Please try again.'); - } - } - - static async handleDistrictSelection(callbackQuery) { - if (!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 LocationService.getLocation(country, city, district); - - if (!location) { - throw new Error('Location not found'); - } - - const categories = await CategoryService.getCategoriesByLocationId(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 bot.editMessageText( - 'π¦ Select or add category:', - { - chat_id: chatId, - message_id: messageId, - reply_markup: keyboard - } - ); - } catch (error) { - console.error('Error in handleDistrictSelection:', error); - await bot.sendMessage(chatId, 'Error loading categories. Please try again.'); - } - } - - static async handleCategoryInput(msg) { - const chatId = msg.chat.id; - const state = userStates.get(chatId); - - if (!state || !state.action?.startsWith('add_category_')) { - return false; - } - - if (!isAdmin(msg.from.id)) { - await bot.sendMessage(chatId, 'Unauthorized access.'); - return; - } - - try { - const locationId = state.action.replace('add_category_', ''); - - if (!Validators.isValidString(msg.text, 255)) { - await bot.sendMessage(chatId, 'ΠΡΠΈΠ±ΠΊΠ°: Π½Π΅Π΄ΠΎΠΏΡΡΡΠΈΠΌΠΎΠ΅ Π½Π°Π·Π²Π°Π½ΠΈΠ΅ ΠΊΠ°ΡΠ΅Π³ΠΎΡΠΈΠΈ'); - return true; - } - - await db.runAsync( - 'INSERT INTO categories (location_id, name) VALUES (?, ?)', - [locationId, msg.text] - ); - - const location = await LocationService.getLocationById(locationId); - - await 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}` - } - ]] - } - } - ); - - userStates.delete(chatId); - } catch (error) { - if (error.code === 'SQLITE_CONSTRAINT') { - await bot.sendMessage(chatId, 'This category already exists in this location.'); - } else { - console.error('Error adding category:', error); - await bot.sendMessage(chatId, 'Error adding category. Please try again.'); - } - } - - return true; - } - - static async handleAddCategory(callbackQuery) { - if (!isAdmin(callbackQuery.from.id)) { - return; - } - - const chatId = callbackQuery.message.chat.id; - const locationId = callbackQuery.data.replace('add_category_', ''); - - userStates.set(chatId, {action: `add_category_${locationId}`}); - - 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_${location.country}_${location.city}_${location.district}`} - ]] - } - } - ); - } - // ΠΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΠ΅ ΠΊΠ°ΡΠ΅Π³ΠΎΡΠΈΠΈ - static async handleCategoryUpdate(msg) { - const chatId = msg.chat.id; - const state = userStates.get(chatId); - - if (!state || !state.action?.startsWith('edit_category_')) { - console.log('[DEBUG] Invalid state or action:', state); - return false; - } - - if (!isAdmin(msg.from.id)) { - await bot.sendMessage(chatId, 'ΠΠ΅Π°Π²ΡΠΎΡΠΈΠ·ΠΎΠ²Π°Π½Π½ΡΠΉ Π΄ΠΎΡΡΡΠΏ.'); - return; - } - - try { - const [locationId, categoryId] = state.action.replace('edit_category_', '').split('_'); - - console.log('[DEBUG] Updating category:', { locationId, categoryId, newName: msg.text }); - - if (!Validators.isValidString(msg.text, 255)) { - await bot.sendMessage(chatId, 'ΠΡΠΈΠ±ΠΊΠ°: Π½Π΅Π΄ΠΎΠΏΡΡΡΠΈΠΌΠΎΠ΅ Π½Π°Π·Π²Π°Π½ΠΈΠ΅ ΠΊΠ°ΡΠ΅Π³ΠΎΡΠΈΠΈ'); - return true; - } - - await db.runAsync( - 'UPDATE categories SET name = ? WHERE id = ? AND location_id = ?', - [msg.text, categoryId, locationId] - ); - - console.log('[DEBUG] Category updated successfully'); - - await bot.sendMessage( - chatId, - `β ΠΠ°Π·Π²Π°Π½ΠΈΠ΅ ΠΊΠ°ΡΠ΅Π³ΠΎΡΠΈΠΈ ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΎ Π½Π° "${msg.text}".`, - { - reply_markup: { - inline_keyboard: [[ - { - text: 'Β« ΠΠ°Π·Π°Π΄ ΠΊ ΠΊΠ°ΡΠ΅Π³ΠΎΡΠΈΡΠΌ', - callback_data: `prod_category_${locationId}_${categoryId}` - } - ]] - } - } - ); - - userStates.delete(chatId); - } catch (error) { - console.error('[ERROR] Error updating category:', error); - await bot.sendMessage(chatId, 'ΠΡΠΈΠ±ΠΊΠ° ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΡ ΠΊΠ°ΡΠ΅Π³ΠΎΡΠΈΠΈ. ΠΠΎΠΆΠ°Π»ΡΠΉΡΡΠ°, ΠΏΠΎΠΏΡΠΎΠ±ΡΠΉΡΠ΅ ΡΠ½ΠΎΠ²Π°.'); - } - - return true; - } - // Π Π΅Π΄Π°ΠΊΡΠΈΡΠΎΠ²Π°Π½ΠΈΠ΅ ΠΊΠ°ΡΠ΅Π³ΠΎΡΠΈΠΈ - static async handleEditCategory(callbackQuery) { - if (!isAdmin(callbackQuery.from.id)) { - return; - } - - const chatId = callbackQuery.message.chat.id; - const [locationId, categoryId] = callbackQuery.data.replace('edit_category_', '').split('_'); - - userStates.set(chatId, { action: `edit_category_${locationId}_${categoryId}` }); - - await bot.editMessageText( - 'ΠΠΎΠΆΠ°Π»ΡΠΉΡΡΠ°, Π²Π²Π΅Π΄ΠΈΡΠ΅ Π½ΠΎΠ²ΠΎΠ΅ Π½Π°Π·Π²Π°Π½ΠΈΠ΅ ΠΊΠ°ΡΠ΅Π³ΠΎΡΠΈΠΈ:', - { - chat_id: chatId, - message_id: callbackQuery.message.message_id, - reply_markup: { - inline_keyboard: [[ - { text: 'β ΠΡΠΌΠ΅Π½Π°', callback_data: `prod_category_${locationId}_${categoryId}` } - ]] - } - } - ); - } - static async handleCategorySelection(callbackQuery) { - if (!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 category = await CategoryService.getCategoryById(categoryId); - const location = await LocationService.getLocationById(locationId); - - // ΠΠΎΠ»ΡΡΠ°Π΅ΠΌ ΡΠΎΠ²Π°ΡΡ Π΄Π»Ρ Π²ΡΠ±ΡΠ°Π½Π½ΠΎΠΉ ΠΊΠ°ΡΠ΅Π³ΠΎΡΠΈΠΈ - const products = await ProductService.getProductsByCategoryId(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: 'β Add Product', callback_data: `add_product_${locationId}_${categoryId}` }], - [{ text: 'Β« Back', callback_data: `prod_district_${location.country}_${location.city}_${location.district}` }] - ] - }; - - await bot.editMessageText( - `π¦ Category: ${category.name}\nSelect or add product:`, - { - chat_id: chatId, - message_id: messageId, - reply_markup: keyboard - } - ); - } catch (error) { - console.error('Error in handleCategorySelection:', error); - await bot.sendMessage(chatId, 'Error loading products. Please try again.'); - } - } - - static async handleSubcategoryInput(msg) { - const chatId = msg.chat.id; - const state = userStates.get(chatId); - - if (!state || !state.action?.startsWith('add_subcategory_')) { - return false; - } - - if (!isAdmin(msg.from.id)) { - await bot.sendMessage(chatId, 'Unauthorized access.'); - return; - } - - try { - const [locationId, categoryId] = state.action.replace('add_subcategory_', '').split('_'); - - if (!Validators.isValidString(msg.text, 255)) { - await bot.sendMessage(chatId, 'ΠΡΠΈΠ±ΠΊΠ°: Π½Π΅Π΄ΠΎΠΏΡΡΡΠΈΠΌΠΎΠ΅ Π½Π°Π·Π²Π°Π½ΠΈΠ΅ ΠΏΠΎΠ΄ΠΊΠ°ΡΠ΅Π³ΠΎΡΠΈΠΈ'); - return true; - } - - await db.runAsync( - 'INSERT INTO subcategories (category_id, name) VALUES (?, ?)', - [categoryId, msg.text] - ); - - await bot.sendMessage( - chatId, - `β Subcategory "${msg.text}" added successfully!`, - { - reply_markup: { - inline_keyboard: [[ - { - text: 'Β« Back to Subcategories', - callback_data: `prod_category_${locationId}_${categoryId}` - } - ]] - } - } - ); - - userStates.delete(chatId); - } catch (error) { - if (error.code === 'SQLITE_CONSTRAINT') { - await bot.sendMessage(chatId, 'This subcategory already exists in this category.'); - } else { - console.error('Error adding subcategory:', error); - await bot.sendMessage(chatId, 'Error adding subcategory. Please try again.'); - } - } - - return true; - } - - static async handleAddSubcategory(callbackQuery) { - if (!isAdmin(callbackQuery.from.id)) { - return; - } - - const chatId = callbackQuery.message.chat.id; - const [locationId, categoryId] = callbackQuery.data.replace('add_subcategory_', '').split('_'); - - userStates.set(chatId, {action: `add_subcategory_${locationId}_${categoryId}`}); - - await 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}`} - ]] - } - } - ); - } - - static 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 - LIMIT ? - OFFSET ? - `, - [locationId, categoryId, subcategoryId, limit, offset] - ); - - if ((products.length === 0) && (page == 0)) { - return { - text: 'No products for this location', - markup: { - inline_keyboard: [ - [{ - 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); - } - - 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.'}; - } - } - - static async handleCategorySelection(callbackQuery) { - if (!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 category = await CategoryService.getCategoryById(categoryId); - const location = await LocationService.getLocationById(locationId); - - // ΠΠΎΠ»ΡΡΠ°Π΅ΠΌ ΡΠΎΠ²Π°ΡΡ Π΄Π»Ρ Π²ΡΠ±ΡΠ°Π½Π½ΠΎΠΉ ΠΊΠ°ΡΠ΅Π³ΠΎΡΠΈΠΈ - const products = await ProductService.getProductsByCategoryId(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: 'β Add Product', callback_data: `add_product_${locationId}_${categoryId}` }], - [{ text: 'Β« Back', callback_data: `prod_district_${location.country}_${location.city}_${location.district}` }] // ΠΡΠΏΡΠ°Π²Π»Π΅Π½ΠΎ Π½Π° ΠΊΠ°ΡΠ΅Π³ΠΎΡΠΈΡ - ] - }; - - await bot.editMessageText( - `π¦ Category: ${category.name}\nSelect or add product:`, - { - chat_id: chatId, - message_id: messageId, - reply_markup: keyboard - } - ); - } catch (error) { - console.error('Error in handleCategorySelection:', error); - await bot.sendMessage(chatId, 'Error loading products. Please try again.'); - } - } - - static async handleProductListPage(callbackQuery) { - if (!isAdmin(callbackQuery.from.id)) { - return; - } - - const chatId = callbackQuery.message.chat.id; - - const [locationId, categoryId, subcategoryId, page] = callbackQuery.data.replace('list_products_', '').split("_"); - - try { - const {text, markup} = await this.viewProductsPage(locationId, categoryId, subcategoryId, parseInt(page)); - await bot.editMessageText(text, { - chat_id: chatId, - message_id: callbackQuery.message.message_id, - reply_markup: markup, - parse_mode: 'HTML' - }); - } catch (e) { - return; - } - } - - static async handleAddProduct(callbackQuery) { - if (!isAdmin(callbackQuery.from.id)) { - return; - } - - const chatId = callbackQuery.message.chat.id; - const messageId = callbackQuery.message.message_id; - const [locationId, categoryId] = callbackQuery.data.replace('add_product_', '').split('_'); - - try { - 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 add 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`;
-
- userStates.set(chatId, {
- action: 'import_products',
- locationId,
- categoryId
- });
-
- await bot.editMessageText(message, {
- chat_id: chatId,
- message_id: messageId,
- parse_mode: 'HTML',
- reply_markup: {
- inline_keyboard: [[
- {
- text: 'β Cancel',
- callback_data: `prod_category_${locationId}_${categoryId}`
- }
- ]]
- }
- });
- } catch (error) {
- console.error('Error in handleAddProduct:', error);
- await bot.sendMessage(chatId, 'Error preparing product import. Please try again.');
- }
- }
-
- static async handleProductImport(msg) {
- const chatId = msg.chat.id;
- const state = userStates.get(chatId);
-
- if (!state || state.action !== 'import_products') {
- return false;
- }
-
- if (!isAdmin(msg.from.id)) {
- await bot.sendMessage(chatId, 'Unauthorized access.');
- return;
- }
-
- try {
- let products;
- let jsonContent;
-
- // Handle file upload
- if (msg.document) {
- if (!msg.document.file_name.endsWith('.json')) {
- await bot.sendMessage(chatId, 'Please upload a .json file.');
- return true;
- }
-
- const file = await bot.getFile(msg.document.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 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 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) {
- if (!Validators.isValidString(product.name, 255)) {
- await bot.sendMessage(chatId, `ΠΡΠΈΠ±ΠΊΠ°: Π½Π΅Π΄ΠΎΠΏΡΡΡΠΈΠΌΠΎΠ΅ Π½Π°Π·Π²Π°Π½ΠΈΠ΅ ΡΠΎΠ²Π°ΡΠ° "${product.name}"`);
- await db.runAsync('ROLLBACK');
- return true;
- }
- if (!Validators.isValidPrice(product.price)) {
- await bot.sendMessage(chatId, `ΠΡΠΈΠ±ΠΊΠ°: Π½Π΅Π΄ΠΎΠΏΡΡΡΠΈΠΌΠ°Ρ ΡΠ΅Π½Π° "${product.price}"`);
- await db.runAsync('ROLLBACK');
- return true;
- }
- if (!Number.isFinite(product.quantity_in_stock) || product.quantity_in_stock < 0) {
- await bot.sendMessage(chatId, `ΠΡΠΈΠ±ΠΊΠ°: Π½Π΅Π΄ΠΎΠΏΡΡΡΠΈΠΌΠΎΠ΅ ΠΊΠΎΠ»ΠΈΡΠ΅ΡΡΠ²ΠΎ "${product.quantity_in_stock}"`);
- await db.runAsync('ROLLBACK');
- return true;
- }
- await db.runAsync(
- `INSERT INTO products (
- location_id, category_id,
- name, price, description, private_data,
- quantity_in_stock, photo_url, hidden_photo_url,
- hidden_coordinates, hidden_description
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
- [
- state.locationId, state.categoryId,
- 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 bot.sendMessage(
- chatId,
- `β
Successfully imported ${products.length} products!`,
- {
- reply_markup: {
- inline_keyboard: [[
- {
- text: 'Β« Back to Products',
- callback_data: `prod_category_${state.locationId}_${state.categoryId}`
- }
- ]]
- }
- }
- );
-
- userStates.delete(chatId);
- } catch (error) {
- console.error('Error importing products:', error);
- await bot.sendMessage(chatId, 'Error importing products. Please check the data and try again.');
- await db.runAsync('ROLLBACK');
- }
-
- return true;
- }
-
- static async handleProductEditImport(msg) {
- const chatId = msg.chat.id;
- const state = userStates.get(chatId);
-
- if (!state || state.action !== 'edit_product') {
- return false;
- }
-
- if (!isAdmin(msg.from.id)) {
- await bot.sendMessage(chatId, 'Unauthorized access.');
- return;
- }
-
- try {
- let product;
- let jsonContent;
-
- // Handle file upload
- if (msg.document) {
- if (!msg.document.file_name.endsWith('.json')) {
- await bot.sendMessage(chatId, 'Please upload a .json file.');
- return true;
- }
-
- const file = await bot.getFile(msg.document.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 bot.sendMessage(chatId, 'Please send either a JSON file or JSON text.');
- return true;
- }
-
- try {
- product = JSON.parse(jsonContent);
- } catch (e) {
- await bot.sendMessage(chatId, 'Invalid JSON format. Please check the format and try again.');
- return true;
- }
-
- await db.runAsync('BEGIN TRANSACTION');
-
- if (!Validators.isValidString(product.name, 255)) {
- await bot.sendMessage(chatId, 'ΠΡΠΈΠ±ΠΊΠ°: Π½Π΅Π΄ΠΎΠΏΡΡΡΠΈΠΌΠΎΠ΅ Π½Π°Π·Π²Π°Π½ΠΈΠ΅ ΡΠΎΠ²Π°ΡΠ°');
- await db.runAsync('ROLLBACK');
- return true;
- }
- if (!Validators.isValidPrice(product.price)) {
- await bot.sendMessage(chatId, 'ΠΡΠΈΠ±ΠΊΠ°: Π½Π΅Π΄ΠΎΠΏΡΡΡΠΈΠΌΠ°Ρ ΡΠ΅Π½Π°');
- await db.runAsync('ROLLBACK');
- return true;
- }
-
- await db.runAsync(
- `UPDATE products SET
- location_id = ?,
- category_id = ?,
- name = ?,
- price = ?,
- description = ?,
- private_data = ?,
- quantity_in_stock = ?,
- photo_url = ?,
- hidden_photo_url = ?,
- hidden_coordinates = ?,
- hidden_description = ?
- WHERE
- id = ?
- `,
- [
- state.locationId, state.categoryId,
- 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 bot.sendMessage(
- chatId,
- `β
Successfully edited!`,
- {
- reply_markup: {
- inline_keyboard: [[
- {
- text: 'Β« Back to Products',
- callback_data: `prod_category_${state.locationId}_${state.categoryId}`
- }
- ]]
- }
- }
- );
-
- userStates.delete(chatId);
- } catch (error) {
- console.error('Error importing products:', error);
- await bot.sendMessage(chatId, 'Error importing products. Please check the data and try again.');
- await db.runAsync('ROLLBACK');
- }
-
- return true;
- }
-
- static async handleViewProduct(callbackQuery) {
- if (!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 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:
-
- Name: ${product.name}
- Price: $${product.price}
- Description: ${product.description}
- Stock: ${product.quantity_in_stock}
- Location: ${location.country}, ${location.city}, ${location.district}
- Category: ${product.category_name}
-
- π Private Information:
- ${product.private_data}
- 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_category_${product.location_id}_${product.category_id}` // ΠΡΠΏΡΠ°Π²Π»Π΅Π½ΠΎ Π½Π° ΠΊΠ°ΡΠ΅Π³ΠΎΡΠΈΡ
- }]
- ]
- };
-
- let photoMessage;
- let hiddenPhotoMessage;
-
- // Send product photos
- if (product.photo_url) {
- try {
- photoMessage = await bot.sendPhoto(chatId, product.photo_url, {caption: 'Public photo'});
- } catch (e) {
- photoMessage = await bot.sendPhoto(chatId, "./corrupt-photo.jpg", {caption: 'Public photo'})
- }
- }
- if (product.hidden_photo_url) {
- try {
- hiddenPhotoMessage = await bot.sendPhoto(chatId, product.hidden_photo_url, {caption: 'Hidden photo'});
- } catch (e) {
- hiddenPhotoMessage = await bot.sendPhoto(chatId, "./corrupt-photo.jpg", {caption: 'Hidden photo'})
- }
- }
-
- userStates.set(chatId, {
- msgToDelete: [photoMessage.message_id, hiddenPhotoMessage.message_id]
- })
-
- await bot.deleteMessage(chatId, messageId);
- await bot.sendMessage(chatId, message, {reply_markup: keyboard});
- } catch (error) {
- console.error('Error in handleViewProduct:', error);
- await bot.sendMessage(chatId, 'Error loading product details. Please try again.');
- }
- }
-
- static async handleProductEdit(callbackQuery) {
- if (!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 ProductService.getDetailedProductById(productId);
-
- if (!product) {
- throw new Error('Product not found');
- }
-
- const locationId = product.location_id;
- const categoryId = product.category_id;
-
- const sampleProduct = {
- name: product.name,
- price: product.price,
- description: product.description,
- private_data: product.private_data,
- quantity_in_stock: product.quantity_in_stock,
- photo_url: product.photo_url,
- hidden_photo_url: product.hidden_photo_url,
- hidden_coordinates: product.hidden_coordinates,
- hidden_description: product.hidden_description
- };
-
- 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`;
-
- userStates.set(chatId, {
- action: 'edit_product',
- locationId,
- categoryId,
- productId
- });
-
- await bot.editMessageText(message, {
- chat_id: chatId,
- message_id: messageId,
- parse_mode: 'HTML',
- reply_markup: {
- inline_keyboard: [[
- {
- text: 'β Cancel',
- callback_data: `prod_category_${locationId}_${categoryId}` // ΠΠΎΠ·Π²ΡΠ°ΡΠ°Π΅ΠΌΡΡ ΠΊ ΡΠΏΠΈΡΠΊΡ ΡΠΎΠ²Π°ΡΠΎΠ² Π² ΠΊΠ°ΡΠ΅Π³ΠΎΡΠΈΠΈ
- }
- ]]
- }
- });
- } catch (error) {
- console.error('Error in handleProductEdit:', error);
- await bot.sendMessage(chatId, 'Error loading product details. Please try again.');
- }
- }
-
- static async handleProductDelete(callbackQuery) {
- if (!isAdmin(callbackQuery.from.id)) {
- return;
- }
-
- const productId = callbackQuery.data.replace('delete_product_', '');
- const chatId = callbackQuery.message.chat.id;
-
- try {
- const product = await ProductService.getDetailedProductById(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 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 bot.sendMessage(chatId, 'Error processing delete request. Please try again.');
- }
- }
-
- static async handleConfirmDelete(callbackQuery) {
- if (!isAdmin(callbackQuery.from.id)) {
- return;
- }
-
- const productId = callbackQuery.data.replace('confirm_delete_product_', '');
- const chatId = callbackQuery.message.chat.id;
-
- try {
- const product = await ProductService.getDetailedProductById(productId);
-
- if (!product) {
- throw new Error('Product not found');
- }
-
- const locationId = product.location_id;
- const categoryId = product.category_id;
-
- 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:', e);
- throw e;
- }
-
- const keyboard = {
- inline_keyboard: [
- [{ text: 'Β« Back', callback_data: `prod_category_${locationId}_${categoryId}` }] // ΠΠΎΠ·Π²ΡΠ°ΡΠ°Π΅ΠΌΡΡ ΠΊ ΡΠΏΠΈΡΠΊΡ ΡΠΎΠ²Π°ΡΠΎΠ² Π² ΠΊΠ°ΡΠ΅Π³ΠΎΡΠΈΠΈ
- ]
- };
-
- await 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 bot.sendMessage(chatId, 'Error deleting product. Please try again.');
- }
- }
-}
\ No newline at end of file
diff --git a/src/handlers/adminHandlers/product/categoryAddHandler.js b/src/handlers/adminHandlers/product/categoryAddHandler.js
new file mode 100644
index 0000000..073848a
--- /dev/null
+++ b/src/handlers/adminHandlers/product/categoryAddHandler.js
@@ -0,0 +1,91 @@
+import db from '../../../config/database.js';
+import { isAdmin } from '../../../middleware/auth.js';
+import LocationService from '../../../services/locationService.js';
+import bot from '../../../context/bot.js';
+import userStates from '../../../context/userStates.js';
+import Validators from '../../../utils/validators.js';
+
+export default class CategoryAddHandler {
+
+ static async handleCategoryInput(msg) {
+ const chatId = msg.chat.id;
+ const state = userStates.get(chatId);
+
+ if (!state || !state.action?.startsWith('add_category_')) {
+ return false;
+ }
+
+ if (!isAdmin(msg.from.id)) {
+ await bot.sendMessage(chatId, 'Unauthorized access.');
+ return;
+ }
+
+ try {
+ const locationId = state.action.replace('add_category_', '');
+
+ if (!Validators.isValidString(msg.text, 255)) {
+ await bot.sendMessage(chatId, 'ΠΡΠΈΠ±ΠΊΠ°: Π½Π΅Π΄ΠΎΠΏΡΡΡΠΈΠΌΠΎΠ΅ Π½Π°Π·Π²Π°Π½ΠΈΠ΅ ΠΊΠ°ΡΠ΅Π³ΠΎΡΠΈΠΈ');
+ return true;
+ }
+
+ await db.runAsync(
+ 'INSERT INTO categories (location_id, name) VALUES (?, ?)',
+ [locationId, msg.text]
+ );
+
+ const location = await LocationService.getLocationById(locationId);
+
+ await 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}`
+ }
+ ]]
+ }
+ }
+ );
+
+ userStates.delete(chatId);
+ } catch (error) {
+ if (error.code === 'SQLITE_CONSTRAINT') {
+ await bot.sendMessage(chatId, 'This category already exists in this location.');
+ } else {
+ console.error('Error adding category:', error);
+ await bot.sendMessage(chatId, 'Error adding category. Please try again.');
+ }
+ }
+
+ return true;
+ }
+
+ static async handleAddCategory(callbackQuery) {
+ if (!isAdmin(callbackQuery.from.id)) {
+ return;
+ }
+
+ const chatId = callbackQuery.message.chat.id;
+ const locationId = callbackQuery.data.replace('add_category_', '');
+
+ userStates.set(chatId, {action: `add_category_${locationId}`});
+
+ 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_${location.country}_${location.city}_${location.district}`}
+ ]]
+ }
+ }
+ );
+ }
+}
diff --git a/src/handlers/adminHandlers/product/categoryEditHandler.js b/src/handlers/adminHandlers/product/categoryEditHandler.js
new file mode 100644
index 0000000..f3c9e6b
--- /dev/null
+++ b/src/handlers/adminHandlers/product/categoryEditHandler.js
@@ -0,0 +1,82 @@
+import db from '../../../config/database.js';
+import { isAdmin } from '../../../middleware/auth.js';
+import bot from '../../../context/bot.js';
+import userStates from '../../../context/userStates.js';
+import Validators from '../../../utils/validators.js';
+
+export default class CategoryEditHandler {
+
+ static async handleCategoryUpdate(msg) {
+ const chatId = msg.chat.id;
+ const state = userStates.get(chatId);
+
+ if (!state || !state.action?.startsWith('edit_category_')) {
+ return false;
+ }
+
+ if (!isAdmin(msg.from.id)) {
+ await bot.sendMessage(chatId, 'ΠΠ΅Π°Π²ΡΠΎΡΠΈΠ·ΠΎΠ²Π°Π½Π½ΡΠΉ Π΄ΠΎΡΡΡΠΏ.');
+ return;
+ }
+
+ try {
+ const [locationId, categoryId] = state.action.replace('edit_category_', '').split('_');
+
+ if (!Validators.isValidString(msg.text, 255)) {
+ await bot.sendMessage(chatId, 'ΠΡΠΈΠ±ΠΊΠ°: Π½Π΅Π΄ΠΎΠΏΡΡΡΠΈΠΌΠΎΠ΅ Π½Π°Π·Π²Π°Π½ΠΈΠ΅ ΠΊΠ°ΡΠ΅Π³ΠΎΡΠΈΠΈ');
+ return true;
+ }
+
+ await db.runAsync(
+ 'UPDATE categories SET name = ? WHERE id = ? AND location_id = ?',
+ [msg.text, categoryId, locationId]
+ );
+
+ await bot.sendMessage(
+ chatId,
+ `β
ΠΠ°Π·Π²Π°Π½ΠΈΠ΅ ΠΊΠ°ΡΠ΅Π³ΠΎΡΠΈΠΈ ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΎ Π½Π° "${msg.text}".`,
+ {
+ reply_markup: {
+ inline_keyboard: [[
+ {
+ text: 'Β« ΠΠ°Π·Π°Π΄ ΠΊ ΠΊΠ°ΡΠ΅Π³ΠΎΡΠΈΡΠΌ',
+ callback_data: `prod_category_${locationId}_${categoryId}`
+ }
+ ]]
+ }
+ }
+ );
+
+ userStates.delete(chatId);
+ } catch (error) {
+ console.error('Error updating category:', error);
+ await bot.sendMessage(chatId, 'ΠΡΠΈΠ±ΠΊΠ° ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΈΡ ΠΊΠ°ΡΠ΅Π³ΠΎΡΠΈΠΈ. ΠΠΎΠΆΠ°Π»ΡΠΉΡΡΠ°, ΠΏΠΎΠΏΡΠΎΠ±ΡΠΉΡΠ΅ ΡΠ½ΠΎΠ²Π°.');
+ }
+
+ return true;
+ }
+
+ static async handleEditCategory(callbackQuery) {
+ if (!isAdmin(callbackQuery.from.id)) {
+ return;
+ }
+
+ const chatId = callbackQuery.message.chat.id;
+ const [locationId, categoryId] = callbackQuery.data.replace('edit_category_', '').split('_');
+
+ userStates.set(chatId, { action: `edit_category_${locationId}_${categoryId}` });
+
+ await bot.editMessageText(
+ 'ΠΠΎΠΆΠ°Π»ΡΠΉΡΡΠ°, Π²Π²Π΅Π΄ΠΈΡΠ΅ Π½ΠΎΠ²ΠΎΠ΅ Π½Π°Π·Π²Π°Π½ΠΈΠ΅ ΠΊΠ°ΡΠ΅Π³ΠΎΡΠΈΠΈ:',
+ {
+ chat_id: chatId,
+ message_id: callbackQuery.message.message_id,
+ reply_markup: {
+ inline_keyboard: [[
+ { text: 'β ΠΡΠΌΠ΅Π½Π°', callback_data: `prod_category_${locationId}_${categoryId}` }
+ ]]
+ }
+ }
+ );
+ }
+}
diff --git a/src/handlers/adminHandlers/product/categorySelectionHandler.js b/src/handlers/adminHandlers/product/categorySelectionHandler.js
new file mode 100644
index 0000000..5ee0498
--- /dev/null
+++ b/src/handlers/adminHandlers/product/categorySelectionHandler.js
@@ -0,0 +1,48 @@
+import { isAdmin } from '../../../middleware/auth.js';
+import LocationService from '../../../services/locationService.js';
+import bot from '../../../context/bot.js';
+import CategoryService from '../../../services/categoryService.js';
+import ProductService from '../../../services/productService.js';
+
+export default class CategorySelectionHandler {
+
+ static async handleCategorySelection(callbackQuery) {
+ if (!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 category = await CategoryService.getCategoryById(categoryId);
+ const location = await LocationService.getLocationById(locationId);
+
+ const products = await ProductService.getProductsByCategoryId(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: 'β Add Product', callback_data: `add_product_${locationId}_${categoryId}` }],
+ [{ text: 'Β« Back', callback_data: `prod_district_${location.country}_${location.city}_${location.district}` }]
+ ]
+ };
+
+ await bot.editMessageText(
+ `π¦ Category: ${category.name}\nSelect or add product:`,
+ {
+ chat_id: chatId,
+ message_id: messageId,
+ reply_markup: keyboard
+ }
+ );
+ } catch (error) {
+ console.error('Error in handleCategorySelection:', error);
+ await bot.sendMessage(chatId, 'Error loading products. Please try again.');
+ }
+ }
+}
diff --git a/src/handlers/adminHandlers/product/createHandler.js b/src/handlers/adminHandlers/product/createHandler.js
new file mode 100644
index 0000000..1f7b786
--- /dev/null
+++ b/src/handlers/adminHandlers/product/createHandler.js
@@ -0,0 +1,56 @@
+import { isAdmin } from '../../../middleware/auth.js';
+import bot from '../../../context/bot.js';
+import userStates from '../../../context/userStates.js';
+
+export default class CreateHandler {
+
+ static async handleAddProduct(callbackQuery) {
+ if (!isAdmin(callbackQuery.from.id)) {
+ return;
+ }
+
+ const chatId = callbackQuery.message.chat.id;
+ const messageId = callbackQuery.message.message_id;
+ const [locationId, categoryId] = callbackQuery.data.replace('add_product_', '').split('_');
+
+ try {
+ 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 add 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`;
+
+ userStates.set(chatId, {
+ action: 'import_products',
+ locationId,
+ categoryId
+ });
+
+ await bot.editMessageText(message, {
+ chat_id: chatId,
+ message_id: messageId,
+ parse_mode: 'HTML',
+ reply_markup: {
+ inline_keyboard: [[
+ {
+ text: 'β Cancel',
+ callback_data: `prod_category_${locationId}_${categoryId}`
+ }
+ ]]
+ }
+ });
+ } catch (error) {
+ console.error('Error in handleAddProduct:', error);
+ await bot.sendMessage(chatId, 'Error preparing product import. Please try again.');
+ }
+ }
+}
diff --git a/src/handlers/adminHandlers/product/deleteHandler.js b/src/handlers/adminHandlers/product/deleteHandler.js
new file mode 100644
index 0000000..6d832f3
--- /dev/null
+++ b/src/handlers/adminHandlers/product/deleteHandler.js
@@ -0,0 +1,97 @@
+import db from '../../../config/database.js';
+import { isAdmin } from '../../../middleware/auth.js';
+import bot from '../../../context/bot.js';
+import ProductService from '../../../services/productService.js';
+
+export default class DeleteHandler {
+
+ static async handleProductDelete(callbackQuery) {
+ if (!isAdmin(callbackQuery.from.id)) {
+ return;
+ }
+
+ const productId = callbackQuery.data.replace('delete_product_', '');
+ const chatId = callbackQuery.message.chat.id;
+
+ try {
+ const product = await ProductService.getDetailedProductById(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_category_${product.location_id}_${product.category_id}`
+ }
+ ]
+ ]
+ };
+
+ await 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 bot.sendMessage(chatId, 'Error processing delete request. Please try again.');
+ }
+ }
+
+ static async handleConfirmDelete(callbackQuery) {
+ if (!isAdmin(callbackQuery.from.id)) {
+ return;
+ }
+
+ const productId = callbackQuery.data.replace('confirm_delete_product_', '');
+ const chatId = callbackQuery.message.chat.id;
+
+ try {
+ const product = await ProductService.getDetailedProductById(productId);
+
+ if (!product) {
+ throw new Error('Product not found');
+ }
+
+ const locationId = product.location_id;
+ const categoryId = product.category_id;
+
+ 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:', e);
+ throw e;
+ }
+
+ const keyboard = {
+ inline_keyboard: [
+ [{ text: 'Β« Back', callback_data: `prod_category_${locationId}_${categoryId}` }]
+ ]
+ };
+
+ await 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 bot.sendMessage(chatId, 'Error deleting product. Please try again.');
+ }
+ }
+}
diff --git a/src/handlers/adminHandlers/product/districtHandler.js b/src/handlers/adminHandlers/product/districtHandler.js
new file mode 100644
index 0000000..742c893
--- /dev/null
+++ b/src/handlers/adminHandlers/product/districtHandler.js
@@ -0,0 +1,89 @@
+import { isAdmin } from '../../../middleware/auth.js';
+import LocationService from '../../../services/locationService.js';
+import CategoryService from '../../../services/categoryService.js';
+import bot from '../../../context/bot.js';
+import userStates from '../../../context/userStates.js';
+
+export default class DistrictHandler {
+
+ static async handleCitySelection(callbackQuery) {
+ if (!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 LocationService.getDistrictsByCountryAndCity(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 bot.editMessageText(
+ `π Select district in ${city}:`,
+ {
+ chat_id: chatId,
+ message_id: messageId,
+ reply_markup: keyboard
+ }
+ );
+ } catch (error) {
+ console.error('Error in handleCitySelection:', error);
+ await bot.sendMessage(chatId, 'Error loading districts. Please try again.');
+ }
+ }
+
+ static async handleDistrictSelection(callbackQuery) {
+ if (!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 LocationService.getLocation(country, city, district);
+
+ if (!location) {
+ throw new Error('Location not found');
+ }
+
+ const categories = await CategoryService.getCategoriesByLocationId(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 bot.editMessageText(
+ 'π¦ Select or add category:',
+ {
+ chat_id: chatId,
+ message_id: messageId,
+ reply_markup: keyboard
+ }
+ );
+ } catch (error) {
+ console.error('Error in handleDistrictSelection:', error);
+ await bot.sendMessage(chatId, 'Error loading categories. Please try again.');
+ }
+ }
+}
diff --git a/src/handlers/adminHandlers/product/editImportHandler.js b/src/handlers/adminHandlers/product/editImportHandler.js
new file mode 100644
index 0000000..5a1c73d
--- /dev/null
+++ b/src/handlers/adminHandlers/product/editImportHandler.js
@@ -0,0 +1,95 @@
+import db from '../../../config/database.js';
+import { isAdmin } from '../../../middleware/auth.js';
+import fs from 'fs/promises';
+import bot from '../../../context/bot.js';
+import userStates from '../../../context/userStates.js';
+import { validateProductName, validateProductPrice } from './productValidator.js';
+
+export default class EditImportHandler {
+ static async handleProductEditImport(msg) {
+ const chatId = msg.chat.id;
+ const state = userStates.get(chatId);
+
+ if (!state || state.action !== 'edit_product') {
+ return false;
+ }
+
+ if (!isAdmin(msg.from.id)) {
+ await bot.sendMessage(chatId, 'Unauthorized access.');
+ return;
+ }
+
+ try {
+ let product;
+ let jsonContent;
+
+ if (msg.document) {
+ if (!msg.document.file_name.endsWith('.json')) {
+ await bot.sendMessage(chatId, 'Please upload a .json file.');
+ return true;
+ }
+
+ const file = await bot.getFile(msg.document.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 bot.sendMessage(chatId, 'Please send either a JSON file or JSON text.');
+ return true;
+ }
+
+ try {
+ product = JSON.parse(jsonContent);
+ } catch (e) {
+ await bot.sendMessage(chatId, 'Invalid JSON format. Please check the format and try again.');
+ return true;
+ }
+
+ await db.runAsync('BEGIN TRANSACTION');
+
+ if (!validateProductName(product.name, chatId)) {
+ await db.runAsync('ROLLBACK');
+ return true;
+ }
+ if (!validateProductPrice(product.price, chatId)) {
+ await db.runAsync('ROLLBACK');
+ return true;
+ }
+
+ await db.runAsync(
+ `UPDATE products SET
+ location_id = ?, category_id = ?,
+ name = ?, price = ?, description = ?, private_data = ?,
+ quantity_in_stock = ?, photo_url = ?, hidden_photo_url = ?,
+ hidden_coordinates = ?, hidden_description = ?
+ WHERE id = ?`,
+ [
+ state.locationId, state.categoryId,
+ 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 bot.sendMessage(chatId, 'β
Successfully edited!', {
+ reply_markup: {
+ inline_keyboard: [[
+ { text: 'Β« Back to Products', callback_data: `prod_category_${state.locationId}_${state.categoryId}` }
+ ]]
+ }
+ });
+
+ userStates.delete(chatId);
+ } catch (error) {
+ console.error('Error importing products:', error);
+ await bot.sendMessage(chatId, 'Error importing products. Please check the data and try again.');
+ await db.runAsync('ROLLBACK');
+ }
+
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/src/handlers/adminHandlers/product/editStartHandler.js b/src/handlers/adminHandlers/product/editStartHandler.js
new file mode 100644
index 0000000..fd22633
--- /dev/null
+++ b/src/handlers/adminHandlers/product/editStartHandler.js
@@ -0,0 +1,63 @@
+import { isAdmin } from '../../../middleware/auth.js';
+import bot from '../../../context/bot.js';
+import userStates from '../../../context/userStates.js';
+import ProductService from '../../../services/productService.js';
+
+export default class EditStartHandler {
+ static async handleProductEdit(callbackQuery) {
+ if (!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 ProductService.getDetailedProductById(productId);
+
+ if (!product) {
+ throw new Error('Product not found');
+ }
+
+ const locationId = product.location_id;
+ const categoryId = product.category_id;
+
+ const sampleProduct = {
+ name: product.name,
+ price: product.price,
+ description: product.description,
+ private_data: product.private_data,
+ quantity_in_stock: product.quantity_in_stock,
+ photo_url: product.photo_url,
+ hidden_photo_url: product.hidden_photo_url,
+ hidden_coordinates: product.hidden_coordinates,
+ hidden_description: product.hidden_description
+ };
+
+ const jsonExample = JSON.stringify(sampleProduct, null, 2);
+ const message = `To edit product, send a JSON file with product data:\n\n${jsonExample}\n\nYou can either:\n1. Send the JSON as text\n2. Upload a .json file`;
+
+ userStates.set(chatId, {
+ action: 'edit_product',
+ locationId,
+ categoryId,
+ productId
+ });
+
+ await bot.editMessageText(message, {
+ chat_id: chatId,
+ message_id: messageId,
+ parse_mode: 'HTML',
+ reply_markup: {
+ inline_keyboard: [[
+ { text: 'β Cancel', callback_data: `prod_category_${locationId}_${categoryId}` }
+ ]]
+ }
+ });
+ } catch (error) {
+ console.error('Error in handleProductEdit:', error);
+ await bot.sendMessage(chatId, 'Error loading product details. Please try again.');
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/handlers/adminHandlers/product/importHandler.js b/src/handlers/adminHandlers/product/importHandler.js
new file mode 100644
index 0000000..3eae8f8
--- /dev/null
+++ b/src/handlers/adminHandlers/product/importHandler.js
@@ -0,0 +1,75 @@
+import db from '../../../config/database.js';
+import { isAdmin } from '../../../middleware/auth.js';
+import fs from 'fs/promises';
+import bot from '../../../context/bot.js';
+import userStates from '../../../context/userStates.js';
+import ProductValidator from './productValidator.js';
+
+export default class ImportHandler {
+
+ static async handleProductImport(msg) {
+ const chatId = msg.chat.id;
+ const state = userStates.get(chatId);
+ if (!state || state.action !== 'import_products') return false;
+ if (!isAdmin(msg.from.id)) {
+ await bot.sendMessage(chatId, 'Unauthorized access.');
+ return;
+ }
+ try {
+ const jsonContent = await this._extractJsonContent(msg, chatId);
+ if (!jsonContent) return true;
+ let products;
+ try {
+ products = JSON.parse(jsonContent);
+ if (!Array.isArray(products)) throw new Error('Input must be an array of products');
+ } catch (e) {
+ await 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) {
+ const error = ProductValidator.validateProduct(product);
+ if (error) {
+ await bot.sendMessage(chatId, error);
+ await db.runAsync('ROLLBACK');
+ return true;
+ }
+ await db.runAsync(
+ `INSERT INTO products (location_id, category_id, name, price, description, private_data, quantity_in_stock, photo_url, hidden_photo_url, hidden_coordinates, hidden_description) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
+ [state.locationId, state.categoryId, 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 bot.sendMessage(chatId, `β
Successfully imported ${products.length} products!`, {
+ reply_markup: {
+ inline_keyboard: [[{ text: 'Β« Back to Products', callback_data: `prod_category_${state.locationId}_${state.categoryId}` }]]
+ }
+ });
+ userStates.delete(chatId);
+ } catch (error) {
+ console.error('Error importing products:', error);
+ await bot.sendMessage(chatId, 'Error importing products. Please check the data and try again.');
+ await db.runAsync('ROLLBACK');
+ }
+ return true;
+ }
+
+ static async _extractJsonContent(msg, chatId) {
+ if (msg.document) {
+ if (!msg.document.file_name.endsWith('.json')) {
+ await bot.sendMessage(chatId, 'Please upload a .json file.');
+ return null;
+ }
+ const file = await bot.getFile(msg.document.file_id);
+ const fileContent = await bot.downloadFile(file.file_id, '.');
+ const jsonContent = await fs.readFile(fileContent, 'utf8');
+ await fs.rm(fileContent);
+ return jsonContent;
+ }
+ if (msg.text) {
+ return msg.text;
+ }
+ await bot.sendMessage(chatId, 'Please send either a JSON file or JSON text.');
+ return null;
+ }
+}
diff --git a/src/handlers/adminHandlers/product/index.js b/src/handlers/adminHandlers/product/index.js
new file mode 100644
index 0000000..d11158a
--- /dev/null
+++ b/src/handlers/adminHandlers/product/index.js
@@ -0,0 +1,32 @@
+import CreateHandler from './createHandler.js';
+import ImportHandler from './importHandler.js';
+import EditStartHandler from './editStartHandler.js';
+import EditImportHandler from './editImportHandler.js';
+import DeleteHandler from './deleteHandler.js';
+import NavigationHandler from './navigationHandler.js';
+import DistrictHandler from './districtHandler.js';
+import CategoryAddHandler from './categoryAddHandler.js';
+import CategoryEditHandler from './categoryEditHandler.js';
+import CategorySelectionHandler from './categorySelectionHandler.js';
+import ViewHandler from './viewHandler.js';
+import ListHandler from './listHandler.js';
+
+export default {
+ handleProductManagement: NavigationHandler.handleProductManagement,
+ handleCountrySelection: NavigationHandler.handleCountrySelection,
+ handleCitySelection: DistrictHandler.handleCitySelection,
+ handleDistrictSelection: DistrictHandler.handleDistrictSelection,
+ handleCategoryInput: CategoryAddHandler.handleCategoryInput,
+ handleAddCategory: CategoryAddHandler.handleAddCategory,
+ handleCategoryUpdate: CategoryEditHandler.handleCategoryUpdate,
+ handleEditCategory: CategoryEditHandler.handleEditCategory,
+ handleCategorySelection: CategorySelectionHandler.handleCategorySelection,
+ handleAddProduct: CreateHandler.handleAddProduct,
+ handleProductImport: ImportHandler.handleProductImport,
+ handleProductEdit: EditStartHandler.handleProductEdit,
+ handleProductEditImport: EditImportHandler.handleProductEditImport,
+ handleViewProduct: ViewHandler.handleViewProduct,
+ handleProductListPage: ListHandler.handleProductListPage,
+ handleProductDelete: DeleteHandler.handleProductDelete,
+ handleConfirmDelete: DeleteHandler.handleConfirmDelete,
+};
\ No newline at end of file
diff --git a/src/handlers/adminHandlers/product/listHandler.js b/src/handlers/adminHandlers/product/listHandler.js
new file mode 100644
index 0000000..738dc18
--- /dev/null
+++ b/src/handlers/adminHandlers/product/listHandler.js
@@ -0,0 +1,90 @@
+import db from '../../../config/database.js';
+import { isAdmin } from '../../../middleware/auth.js';
+import bot from '../../../context/bot.js';
+
+export default class ListHandler {
+
+ static async viewProductsPage(locationId, categoryId, 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 = ?
+ ORDER BY name
+ LIMIT ? OFFSET ?`,
+ [locationId, categoryId, limit, offset]
+ );
+
+ if (products.length === 0 && page === 0) {
+ return {
+ text: 'No products for this location',
+ markup: {
+ inline_keyboard: [
+ [{ text: 'π₯ Import Products', callback_data: `add_product_${locationId}_${categoryId}` }],
+ [{ text: 'Β« Back', callback_data: `prod_category_${locationId}_${categoryId}` }]
+ ]
+ }
+ };
+ }
+
+ if (products.length === 0 && page > 0) {
+ return await this.viewProductsPage(locationId, categoryId, previousPage);
+ }
+
+ 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}` }]
+ ]
+ };
+
+ keyboard.inline_keyboard.push([
+ { text: 'Β«', callback_data: `list_products_${locationId}_${categoryId}_${previousPage}` },
+ { text: 'Β»', callback_data: `list_products_${locationId}_${categoryId}_${nextPage}` }
+ ]);
+
+ keyboard.inline_keyboard.push([
+ { text: 'Β« Back', callback_data: `prod_category_${locationId}_${categoryId}` }
+ ]);
+
+ return {
+ text: `π¦ ${category.name}\nSelect product or import new ones:`,
+ markup: keyboard
+ };
+ } catch (error) {
+ console.error('Error in viewProductsPage:', error);
+ return { text: 'Error loading products. Please try again.' };
+ }
+ }
+
+ static async handleProductListPage(callbackQuery) {
+ if (!isAdmin(callbackQuery.from.id)) {
+ return;
+ }
+
+ const chatId = callbackQuery.message.chat.id;
+ const [locationId, categoryId, page] = callbackQuery.data.replace('list_products_', '').split('_');
+
+ try {
+ const { text, markup } = await this.viewProductsPage(locationId, categoryId, parseInt(page));
+ await bot.editMessageText(text, {
+ chat_id: chatId,
+ message_id: callbackQuery.message.message_id,
+ reply_markup: markup,
+ parse_mode: 'HTML'
+ });
+ } catch (e) {
+ return;
+ }
+ }
+}
diff --git a/src/handlers/adminHandlers/product/navigationHandler.js b/src/handlers/adminHandlers/product/navigationHandler.js
new file mode 100644
index 0000000..4628f95
--- /dev/null
+++ b/src/handlers/adminHandlers/product/navigationHandler.js
@@ -0,0 +1,86 @@
+import { isAdmin } from '../../../middleware/auth.js';
+import LocationService from '../../../services/locationService.js';
+import bot from '../../../context/bot.js';
+
+export default class NavigationHandler {
+
+ static async handleProductManagement(msg) {
+ const chatId = msg.chat?.id || msg.message?.chat.id;
+
+ if (!isAdmin(msg.from?.id || msg.message?.from.id)) {
+ await bot.sendMessage(chatId, 'Unauthorized access.');
+ return;
+ }
+
+ try {
+ const countries = await LocationService.getCountries()
+
+ if (countries.length === 0) {
+ await 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 bot.sendMessage(
+ chatId,
+ 'π Select country to manage products:',
+ {reply_markup: keyboard}
+ );
+ } catch (error) {
+ console.error('Error in handleProductManagement:', error);
+ await bot.sendMessage(chatId, 'Error loading locations. Please try again.');
+ }
+ }
+
+ static async handleCountrySelection(callbackQuery) {
+ if (!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 LocationService.getCitiesByCountry(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 bot.editMessageText(
+ `π Select city in ${country}:`,
+ {
+ chat_id: chatId,
+ message_id: messageId,
+ reply_markup: keyboard
+ }
+ );
+ } catch (error) {
+ console.error('Error in handleCountrySelection:', error);
+ await bot.sendMessage(chatId, 'Error loading cities. Please try again.');
+ }
+ }
+}
diff --git a/src/handlers/adminHandlers/product/productValidator.js b/src/handlers/adminHandlers/product/productValidator.js
new file mode 100644
index 0000000..fdb2930
--- /dev/null
+++ b/src/handlers/adminHandlers/product/productValidator.js
@@ -0,0 +1,17 @@
+import Validators from '../../../utils/validators.js';
+
+export default class ProductValidator {
+
+ static validateProduct(product) {
+ if (!Validators.isValidString(product.name, 255)) {
+ return `ΠΡΠΈΠ±ΠΊΠ°: Π½Π΅Π΄ΠΎΠΏΡΡΡΠΈΠΌΠΎΠ΅ Π½Π°Π·Π²Π°Π½ΠΈΠ΅ ΡΠΎΠ²Π°ΡΠ° "${product.name}"`;
+ }
+ if (!Validators.isValidPrice(product.price)) {
+ return `ΠΡΠΈΠ±ΠΊΠ°: Π½Π΅Π΄ΠΎΠΏΡΡΡΠΈΠΌΠ°Ρ ΡΠ΅Π½Π° "${product.price}"`;
+ }
+ if (!Number.isFinite(product.quantity_in_stock) || product.quantity_in_stock < 0) {
+ return `ΠΡΠΈΠ±ΠΊΠ°: Π½Π΅Π΄ΠΎΠΏΡΡΡΠΈΠΌΠΎΠ΅ ΠΊΠΎΠ»ΠΈΡΠ΅ΡΡΠ²ΠΎ "${product.quantity_in_stock}"`;
+ }
+ return null;
+ }
+}
diff --git a/src/handlers/adminHandlers/product/viewHandler.js b/src/handlers/adminHandlers/product/viewHandler.js
new file mode 100644
index 0000000..a1f5b27
--- /dev/null
+++ b/src/handlers/adminHandlers/product/viewHandler.js
@@ -0,0 +1,89 @@
+import { isAdmin } from '../../../middleware/auth.js';
+import bot from '../../../context/bot.js';
+import userStates from '../../../context/userStates.js';
+import LocationService from '../../../services/locationService.js';
+import ProductService from '../../../services/productService.js';
+
+export default class ViewHandler {
+
+ static async handleViewProduct(callbackQuery) {
+ if (!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 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:
+
+ Name: ${product.name}
+ Price: $${product.price}
+ Description: ${product.description}
+ Stock: ${product.quantity_in_stock}
+ Location: ${location.country}, ${location.city}, ${location.district}
+ Category: ${product.category_name}
+
+ π Private Information:
+ ${product.private_data}
+ 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_category_${product.location_id}_${product.category_id}`
+ }]
+ ]
+ };
+
+ let photoMessage;
+ let hiddenPhotoMessage;
+
+ if (product.photo_url) {
+ try {
+ photoMessage = await bot.sendPhoto(chatId, product.photo_url, {caption: 'Public photo'});
+ } catch (e) {
+ photoMessage = await bot.sendPhoto(chatId, "./corrupt-photo.jpg", {caption: 'Public photo'})
+ }
+ }
+ if (product.hidden_photo_url) {
+ try {
+ hiddenPhotoMessage = await bot.sendPhoto(chatId, product.hidden_photo_url, {caption: 'Hidden photo'});
+ } catch (e) {
+ hiddenPhotoMessage = await bot.sendPhoto(chatId, "./corrupt-photo.jpg", {caption: 'Hidden photo'})
+ }
+ }
+
+ userStates.set(chatId, {
+ msgToDelete: [photoMessage.message_id, hiddenPhotoMessage.message_id]
+ })
+
+ await bot.deleteMessage(chatId, messageId);
+ await bot.sendMessage(chatId, message, {reply_markup: keyboard});
+ } catch (error) {
+ console.error('Error in handleViewProduct:', error);
+ await bot.sendMessage(chatId, 'Error loading product details. Please try again.');
+ }
+ }
+}
diff --git a/src/index.js b/src/index.js
index 20d8147..b8a3e96 100644
--- a/src/index.js
+++ b/src/index.js
@@ -15,7 +15,7 @@ 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";
+import productHandler from "./handlers/adminHandlers/product/index.js";
import adminWalletsHandler from "./handlers/adminHandlers/adminWalletsHandler.js";
// Debug logging function
@@ -70,17 +70,17 @@ bot.on('message', async (msg) => {
}
// Check for admin category input
- if (await adminProductHandler.handleCategoryInput(msg)) {
+ if (await productHandler.handleCategoryInput(msg)) {
return;
}
// Check for product import
- if (await adminProductHandler.handleProductImport(msg)) {
+ if (await productHandler.handleProductImport(msg)) {
return;
}
// Check for product edition
- if (await adminProductHandler.handleProductEditImport(msg)) {
+ if (await productHandler.handleProductEditImport(msg)) {
return;
}
@@ -95,7 +95,7 @@ bot.on('message', async (msg) => {
}
// Check for category update input
- if (await adminProductHandler.handleCategoryUpdate(msg)) {
+ if (await productHandler.handleCategoryUpdate(msg)) {
return;
}
@@ -116,7 +116,7 @@ bot.on('message', async (msg) => {
break;
case 'π¦ Manage Products':
if (adminHandler.isAdmin(msg.from.id)) {
- await adminProductHandler.handleProductManagement(msg);
+ await productHandler.handleProductManagement(msg);
}
break;
case 'π₯ Manage Users':
@@ -271,43 +271,43 @@ bot.on('callback_query', async (callbackQuery) => {
// Admin product management
else if (action === 'manage_products') {
logDebug(action, 'handleProductManagement');
- await adminProductHandler.handleProductManagement(callbackQuery);
+ await productHandler.handleProductManagement(callbackQuery);
} else if (action.startsWith('prod_country_')) {
logDebug(action, 'handleCountrySelection');
- await adminProductHandler.handleCountrySelection(callbackQuery);
+ await productHandler.handleCountrySelection(callbackQuery);
} else if (action.startsWith('prod_city_')) {
logDebug(action, 'handleCitySelection');
- await adminProductHandler.handleCitySelection(callbackQuery);
+ await productHandler.handleCitySelection(callbackQuery);
} else if (action.startsWith('prod_district_')) {
logDebug(action, 'handleDistrictSelection');
- await adminProductHandler.handleDistrictSelection(callbackQuery);
+ await productHandler.handleDistrictSelection(callbackQuery);
} else if (action.startsWith('add_category_')) {
logDebug(action, 'handleAddCategory');
- await adminProductHandler.handleAddCategory(callbackQuery);
+ await productHandler.handleAddCategory(callbackQuery);
} else if (action.startsWith('edit_category_')) {
logDebug(action, 'handleEditCategory');
- await adminProductHandler.handleEditCategory(callbackQuery);
+ await productHandler.handleEditCategory(callbackQuery);
} else if (action.startsWith('prod_category_')) {
logDebug(action, 'handleCategorySelection');
- await adminProductHandler.handleCategorySelection(callbackQuery);
+ await productHandler.handleCategorySelection(callbackQuery);
} else if (action.startsWith('list_products_')) {
logDebug(action, 'handleProductListPage');
- await adminProductHandler.handleProductListPage(callbackQuery);
+ await productHandler.handleProductListPage(callbackQuery);
} else if (action.startsWith('add_product_')) {
logDebug(action, 'handleAddProduct');
- await adminProductHandler.handleAddProduct(callbackQuery);
+ await productHandler.handleAddProduct(callbackQuery);
} else if (action.startsWith('view_product_')) {
logDebug(action, 'handleViewProduct');
- await adminProductHandler.handleViewProduct(callbackQuery);
+ await productHandler.handleViewProduct(callbackQuery);
} else if (action.startsWith('edit_product_')) {
logDebug(action, 'handleProductEdit');
- await adminProductHandler.handleProductEdit(callbackQuery)
+ await productHandler.handleProductEdit(callbackQuery)
} else if (action.startsWith('delete_product_')) {
logDebug(action, 'handleProductDelete');
- await adminProductHandler.handleProductDelete(callbackQuery);
+ await productHandler.handleProductDelete(callbackQuery);
} else if (action.startsWith('confirm_delete_product_')) {
logDebug(action, 'handleConfirmDelete');
- await adminProductHandler.handleConfirmDelete(callbackQuery);
+ await productHandler.handleConfirmDelete(callbackQuery);
}
// Admin user management
else if (action.startsWith('view_user_')) {