diff --git a/src/config/database.js b/src/config/database.js index 2917fc0..8bb8c60 100644 --- a/src/config/database.js +++ b/src/config/database.js @@ -165,7 +165,6 @@ const initDb = async () => { id INTEGER PRIMARY KEY AUTOINCREMENT, location_id INTEGER NOT NULL, category_id INTEGER NOT NULL, - subcategory_id INTEGER NOT NULL, name TEXT NOT NULL, description TEXT, private_data TEXT, @@ -177,8 +176,7 @@ const initDb = async () => { hidden_description TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (location_id) REFERENCES locations(id) ON DELETE CASCADE, - FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE CASCADE, - FOREIGN KEY (subcategory_id) REFERENCES subcategories(id) ON DELETE CASCADE + FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE CASCADE ) `); @@ -222,18 +220,6 @@ const initDb = async () => { ) `); - // Create subcategories table - await db.runAsync(` - CREATE TABLE IF NOT EXISTS subcategories ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - category_id INTEGER NOT NULL, - name TEXT NOT NULL, - created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE CASCADE, - UNIQUE(category_id, name) - ) - `); - // Commit transaction await db.runAsync('COMMIT'); console.log('Database tables initialized successfully'); diff --git a/src/handlers/adminHandlers/adminProductHandler.js b/src/handlers/adminHandlers/adminProductHandler.js index 9d88056..192b671 100644 --- a/src/handlers/adminHandlers/adminProductHandler.js +++ b/src/handlers/adminHandlers/adminProductHandler.js @@ -327,33 +327,31 @@ export default class AdminProductHandler { if (!this.isAdmin(callbackQuery.from.id)) { return; } - + const chatId = callbackQuery.message.chat.id; const messageId = callbackQuery.message.message_id; const [locationId, categoryId] = callbackQuery.data.replace('prod_category_', '').split('_'); - + try { - const subcategories = await CategoryService.getSubcategoriesByCategoryId(categoryId); const category = await CategoryService.getCategoryById(categoryId); const location = await LocationService.getLocationById(locationId); - + + // Получаем товары для выбранной категории + const products = await ProductService.getProductsByCategoryId(categoryId); + const keyboard = { inline_keyboard: [ - ...subcategories.map(sub => [{ - text: sub.name, - callback_data: `prod_subcategory_${locationId}_${categoryId}_${sub.id}` + ...products.map(prod => [{ + text: `${prod.name} - $${prod.price} (${prod.quantity_in_stock} left)`, + callback_data: `view_product_${prod.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}` - }] + [{ 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 subcategory:`, + `📦 Category: ${category.name}\nSelect or add product:`, { chat_id: chatId, message_id: messageId, @@ -362,7 +360,7 @@ export default class AdminProductHandler { ); } catch (error) { console.error('Error in handleCategorySelection:', error); - await bot.sendMessage(chatId, 'Error loading subcategories. Please try again.'); + await bot.sendMessage(chatId, 'Error loading products. Please try again.'); } } @@ -517,40 +515,43 @@ export default class AdminProductHandler { } } - static async handleSubcategorySelection(callbackQuery) { + static async handleCategorySelection(callbackQuery) { if (!this.isAdmin(callbackQuery.from.id)) { return; } - + const chatId = callbackQuery.message.chat.id; const messageId = callbackQuery.message.message_id; - const [locationId, categoryId, subcategoryId] = callbackQuery.data.replace('prod_subcategory_', '').split('_'); - - const state = userStates.get(chatId) - - for (const msgId of state?.msgToDelete || []) { - try { - await bot.deleteMessage(chatId, msgId); - } catch (e) { - // ignore if can't delete - } - } - - userStates.delete(chatId); - + const [locationId, categoryId] = callbackQuery.data.replace('prod_category_', '').split('_'); + try { - const {text, markup} = await this.viewProductsPage(locationId, categoryId, subcategoryId, 0); - + 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( - text, + `📦 Category: ${category.name}\nSelect or add product:`, { chat_id: chatId, message_id: messageId, - reply_markup: markup + reply_markup: keyboard } ); } catch (error) { - console.error('Error in handleSubcategorySelection:', error); + console.error('Error in handleCategorySelection:', error); await bot.sendMessage(chatId, 'Error loading products. Please try again.'); } } @@ -581,11 +582,11 @@ export default class AdminProductHandler { if (!this.isAdmin(callbackQuery.from.id)) { return; } - + const chatId = callbackQuery.message.chat.id; const messageId = callbackQuery.message.message_id; - const [locationId, categoryId, subcategoryId] = callbackQuery.data.replace('add_product_', '').split('_'); - + const [locationId, categoryId] = callbackQuery.data.replace('add_product_', '').split('_'); + try { const sampleProducts = [ { @@ -600,17 +601,16 @@ export default class AdminProductHandler { 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`;
-
+ 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,
- subcategoryId
+ categoryId
});
-
+
await bot.editMessageText(message, {
chat_id: chatId,
message_id: messageId,
@@ -619,7 +619,7 @@ export default class AdminProductHandler {
inline_keyboard: [[
{
text: '❌ Cancel',
- callback_data: `prod_subcategory_${locationId}_${categoryId}_${subcategoryId}`
+ callback_data: `prod_category_${locationId}_${categoryId}`
}
]]
}
@@ -633,40 +633,40 @@ export default class AdminProductHandler {
static async handleProductImport(msg) {
const chatId = msg.chat.id;
const state = userStates.get(chatId);
-
+
if (!state || state.action !== 'import_products') {
return false;
}
-
+
if (!this.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)) {
@@ -676,28 +676,28 @@ export default class AdminProductHandler {
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) {
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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
+ 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, state.subcategoryId,
+ 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!`,
@@ -706,96 +706,95 @@ export default class AdminProductHandler {
inline_keyboard: [[
{
text: '« Back to Products',
- callback_data: `prod_subcategory_${state.locationId}_${state.categoryId}_${state.subcategoryId}`
+ 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 (!this.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');
-
+
await db.runAsync(
`UPDATE products SET
- location_id = ?,
- category_id = ?,
- subcategory_id = ?,
- name = ?,
- price = ?,
- description = ?,
- private_data = ?,
- quantity_in_stock = ?,
- photo_url = ?,
- hidden_photo_url = ?,
- hidden_coordinates = ?,
- hidden_description = ?
- WHERE
- id = ?
- `,
+ 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, state.subcategoryId,
+ 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!`,
@@ -804,20 +803,20 @@ export default class AdminProductHandler {
inline_keyboard: [[
{
text: '« Back to Products',
- callback_data: `prod_subcategory_${state.locationId}_${state.categoryId}_${state.subcategoryId}`
+ 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;
}
diff --git a/src/index.js b/src/index.js
index 0df0a90..05f3347 100644
--- a/src/index.js
+++ b/src/index.js
@@ -69,11 +69,6 @@ bot.on('message', async (msg) => {
return;
}
- // Check for admin subcategory input
- if (await adminProductHandler.handleSubcategoryInput(msg)) {
- return;
- }
-
// Check for product import
if (await adminProductHandler.handleProductImport(msg)) {
return;
@@ -217,9 +212,6 @@ bot.on('callback_query', async (callbackQuery) => {
} else if (action.startsWith('shop_category_')) {
logDebug(action, 'handleCategorySelection');
await userProductHandler.handleCategorySelection(callbackQuery);
- } else if (action.startsWith('shop_subcategory_')) {
- logDebug(action, 'handleSubcategorySelection');
- await userProductHandler.handleSubcategorySelection(callbackQuery);
} else if (action.startsWith('shop_product_')) {
logDebug(action, 'handleProductSelection');
await userProductHandler.handleProductSelection(callbackQuery);
@@ -282,12 +274,6 @@ bot.on('callback_query', async (callbackQuery) => {
} else if (action.startsWith('prod_category_')) {
logDebug(action, 'handleCategorySelection');
await adminProductHandler.handleCategorySelection(callbackQuery);
- } else if (action.startsWith('add_subcategory_')) {
- logDebug(action, 'handleAddSubcategory');
- await adminProductHandler.handleAddSubcategory(callbackQuery);
- } else if (action.startsWith('prod_subcategory_')) {
- logDebug(action, 'handleSubcategorySelection');
- await adminProductHandler.handleSubcategorySelection(callbackQuery);
} else if (action.startsWith('list_products_')) {
logDebug(action, 'handleProductListPage');
await adminProductHandler.handleProductListPage(callbackQuery);
diff --git a/src/services/productService.js b/src/services/productService.js
index 52f1669..06632ad 100644
--- a/src/services/productService.js
+++ b/src/services/productService.js
@@ -12,26 +12,36 @@ class ProductService {
static async getDetailedProductById(productId) {
return await db.getAsync(
- `SELECT p.*, c.name as category_name, s.name as subcategory_name
- FROM products p
- JOIN categories c ON p.category_id = c.id
- JOIN subcategories s ON p.subcategory_id = s.id
- WHERE p.id = ?`,
+ `SELECT p.*, c.name as category_name
+ FROM products p
+ JOIN categories c ON p.category_id = c.id
+ WHERE p.id = ?`,
[productId]
);
}
- static async getProductsByLocationAndCategory(locationId, categoryId, subcategoryId) {
+ static async getProductsByLocationAndCategory(locationId, categoryId) {
return await db.allAsync(
`SELECT id, name, price, description, quantity_in_stock, photo_url
- FROM products
- WHERE location_id = ? AND category_id = ? AND subcategory_id = ?
- AND quantity_in_stock > 0
- ORDER BY name`,
- [locationId, categoryId, subcategoryId]
+ FROM products
+ WHERE location_id = ? AND category_id = ?
+ AND quantity_in_stock > 0
+ ORDER BY name`,
+ [locationId, categoryId]
);
-
}
+
+ static async getProductsByCategoryId(categoryId) {
+ return await db.allAsync(
+ `SELECT id, name, price, description, quantity_in_stock, photo_url
+ FROM products
+ WHERE category_id = ?
+ AND quantity_in_stock > 0
+ ORDER BY name`,
+ [categoryId]
+ );
+ }
+
}
export default ProductService
\ No newline at end of file