refactor(arch): split adminProductHandler.js into 13 modular files (#51)
- 1093-line monolith → 13 files (all ≤97 lines) - navigationHandler: product management entry + country selection - districtHandler: city + district selection - categoryAddHandler: add category input + handler - categoryEditHandler: edit category input + handler - categorySelectionHandler: category selection display - createHandler: add product prompt - importHandler: product import (JSON/text/file) - editStartHandler: product edit prompt - editImportHandler: product edit import - deleteHandler: product delete + confirm - viewHandler: product detail view - listHandler: product list with pagination - productValidator: shared validation utilities - index.js: router re-exporting all 17 handler methods - Removed duplicate handleCategorySelection (subcategories table doesn't exist) - Removed handleSubcategoryInput/handleAddSubcategory (references non-existent subcategories table)
This commit is contained in:
File diff suppressed because it is too large
Load Diff
91
src/handlers/adminHandlers/product/categoryAddHandler.js
Normal file
91
src/handlers/adminHandlers/product/categoryAddHandler.js
Normal file
@@ -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}`}
|
||||||
|
]]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
82
src/handlers/adminHandlers/product/categoryEditHandler.js
Normal file
82
src/handlers/adminHandlers/product/categoryEditHandler.js
Normal file
@@ -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}` }
|
||||||
|
]]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
56
src/handlers/adminHandlers/product/createHandler.js
Normal file
56
src/handlers/adminHandlers/product/createHandler.js
Normal file
@@ -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<pre>${jsonExample}</pre>\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.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
97
src/handlers/adminHandlers/product/deleteHandler.js
Normal file
97
src/handlers/adminHandlers/product/deleteHandler.js
Normal file
@@ -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.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
89
src/handlers/adminHandlers/product/districtHandler.js
Normal file
89
src/handlers/adminHandlers/product/districtHandler.js
Normal file
@@ -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.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
95
src/handlers/adminHandlers/product/editImportHandler.js
Normal file
95
src/handlers/adminHandlers/product/editImportHandler.js
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
63
src/handlers/adminHandlers/product/editStartHandler.js
Normal file
63
src/handlers/adminHandlers/product/editStartHandler.js
Normal file
@@ -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<pre>${jsonExample}</pre>\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.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
75
src/handlers/adminHandlers/product/importHandler.js
Normal file
75
src/handlers/adminHandlers/product/importHandler.js
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
32
src/handlers/adminHandlers/product/index.js
Normal file
32
src/handlers/adminHandlers/product/index.js
Normal file
@@ -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,
|
||||||
|
};
|
||||||
90
src/handlers/adminHandlers/product/listHandler.js
Normal file
90
src/handlers/adminHandlers/product/listHandler.js
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
86
src/handlers/adminHandlers/product/navigationHandler.js
Normal file
86
src/handlers/adminHandlers/product/navigationHandler.js
Normal file
@@ -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.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
17
src/handlers/adminHandlers/product/productValidator.js
Normal file
17
src/handlers/adminHandlers/product/productValidator.js
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
89
src/handlers/adminHandlers/product/viewHandler.js
Normal file
89
src/handlers/adminHandlers/product/viewHandler.js
Normal file
@@ -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.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
38
src/index.js
38
src/index.js
@@ -15,7 +15,7 @@ import adminHandler from "./handlers/adminHandlers/adminHandler.js";
|
|||||||
import adminUserLocationHandler from "./handlers/adminHandlers/adminUserLocationHandler.js";
|
import adminUserLocationHandler from "./handlers/adminHandlers/adminUserLocationHandler.js";
|
||||||
import adminDumpHandler from "./handlers/adminHandlers/adminDumpHandler.js";
|
import adminDumpHandler from "./handlers/adminHandlers/adminDumpHandler.js";
|
||||||
import adminLocationHandler from "./handlers/adminHandlers/adminLocationHandler.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";
|
import adminWalletsHandler from "./handlers/adminHandlers/adminWalletsHandler.js";
|
||||||
|
|
||||||
// Debug logging function
|
// Debug logging function
|
||||||
@@ -70,17 +70,17 @@ bot.on('message', async (msg) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check for admin category input
|
// Check for admin category input
|
||||||
if (await adminProductHandler.handleCategoryInput(msg)) {
|
if (await productHandler.handleCategoryInput(msg)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for product import
|
// Check for product import
|
||||||
if (await adminProductHandler.handleProductImport(msg)) {
|
if (await productHandler.handleProductImport(msg)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for product edition
|
// Check for product edition
|
||||||
if (await adminProductHandler.handleProductEditImport(msg)) {
|
if (await productHandler.handleProductEditImport(msg)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,7 +95,7 @@ bot.on('message', async (msg) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check for category update input
|
// Check for category update input
|
||||||
if (await adminProductHandler.handleCategoryUpdate(msg)) {
|
if (await productHandler.handleCategoryUpdate(msg)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,7 +116,7 @@ bot.on('message', async (msg) => {
|
|||||||
break;
|
break;
|
||||||
case '📦 Manage Products':
|
case '📦 Manage Products':
|
||||||
if (adminHandler.isAdmin(msg.from.id)) {
|
if (adminHandler.isAdmin(msg.from.id)) {
|
||||||
await adminProductHandler.handleProductManagement(msg);
|
await productHandler.handleProductManagement(msg);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case '👥 Manage Users':
|
case '👥 Manage Users':
|
||||||
@@ -271,43 +271,43 @@ bot.on('callback_query', async (callbackQuery) => {
|
|||||||
// Admin product management
|
// Admin product management
|
||||||
else if (action === 'manage_products') {
|
else if (action === 'manage_products') {
|
||||||
logDebug(action, 'handleProductManagement');
|
logDebug(action, 'handleProductManagement');
|
||||||
await adminProductHandler.handleProductManagement(callbackQuery);
|
await productHandler.handleProductManagement(callbackQuery);
|
||||||
} else if (action.startsWith('prod_country_')) {
|
} else if (action.startsWith('prod_country_')) {
|
||||||
logDebug(action, 'handleCountrySelection');
|
logDebug(action, 'handleCountrySelection');
|
||||||
await adminProductHandler.handleCountrySelection(callbackQuery);
|
await productHandler.handleCountrySelection(callbackQuery);
|
||||||
} else if (action.startsWith('prod_city_')) {
|
} else if (action.startsWith('prod_city_')) {
|
||||||
logDebug(action, 'handleCitySelection');
|
logDebug(action, 'handleCitySelection');
|
||||||
await adminProductHandler.handleCitySelection(callbackQuery);
|
await productHandler.handleCitySelection(callbackQuery);
|
||||||
} else if (action.startsWith('prod_district_')) {
|
} else if (action.startsWith('prod_district_')) {
|
||||||
logDebug(action, 'handleDistrictSelection');
|
logDebug(action, 'handleDistrictSelection');
|
||||||
await adminProductHandler.handleDistrictSelection(callbackQuery);
|
await productHandler.handleDistrictSelection(callbackQuery);
|
||||||
} else if (action.startsWith('add_category_')) {
|
} else if (action.startsWith('add_category_')) {
|
||||||
logDebug(action, 'handleAddCategory');
|
logDebug(action, 'handleAddCategory');
|
||||||
await adminProductHandler.handleAddCategory(callbackQuery);
|
await productHandler.handleAddCategory(callbackQuery);
|
||||||
} else if (action.startsWith('edit_category_')) {
|
} else if (action.startsWith('edit_category_')) {
|
||||||
logDebug(action, 'handleEditCategory');
|
logDebug(action, 'handleEditCategory');
|
||||||
await adminProductHandler.handleEditCategory(callbackQuery);
|
await productHandler.handleEditCategory(callbackQuery);
|
||||||
} else if (action.startsWith('prod_category_')) {
|
} else if (action.startsWith('prod_category_')) {
|
||||||
logDebug(action, 'handleCategorySelection');
|
logDebug(action, 'handleCategorySelection');
|
||||||
await adminProductHandler.handleCategorySelection(callbackQuery);
|
await productHandler.handleCategorySelection(callbackQuery);
|
||||||
} else if (action.startsWith('list_products_')) {
|
} else if (action.startsWith('list_products_')) {
|
||||||
logDebug(action, 'handleProductListPage');
|
logDebug(action, 'handleProductListPage');
|
||||||
await adminProductHandler.handleProductListPage(callbackQuery);
|
await productHandler.handleProductListPage(callbackQuery);
|
||||||
} else if (action.startsWith('add_product_')) {
|
} else if (action.startsWith('add_product_')) {
|
||||||
logDebug(action, 'handleAddProduct');
|
logDebug(action, 'handleAddProduct');
|
||||||
await adminProductHandler.handleAddProduct(callbackQuery);
|
await productHandler.handleAddProduct(callbackQuery);
|
||||||
} else if (action.startsWith('view_product_')) {
|
} else if (action.startsWith('view_product_')) {
|
||||||
logDebug(action, 'handleViewProduct');
|
logDebug(action, 'handleViewProduct');
|
||||||
await adminProductHandler.handleViewProduct(callbackQuery);
|
await productHandler.handleViewProduct(callbackQuery);
|
||||||
} else if (action.startsWith('edit_product_')) {
|
} else if (action.startsWith('edit_product_')) {
|
||||||
logDebug(action, 'handleProductEdit');
|
logDebug(action, 'handleProductEdit');
|
||||||
await adminProductHandler.handleProductEdit(callbackQuery)
|
await productHandler.handleProductEdit(callbackQuery)
|
||||||
} else if (action.startsWith('delete_product_')) {
|
} else if (action.startsWith('delete_product_')) {
|
||||||
logDebug(action, 'handleProductDelete');
|
logDebug(action, 'handleProductDelete');
|
||||||
await adminProductHandler.handleProductDelete(callbackQuery);
|
await productHandler.handleProductDelete(callbackQuery);
|
||||||
} else if (action.startsWith('confirm_delete_product_')) {
|
} else if (action.startsWith('confirm_delete_product_')) {
|
||||||
logDebug(action, 'handleConfirmDelete');
|
logDebug(action, 'handleConfirmDelete');
|
||||||
await adminProductHandler.handleConfirmDelete(callbackQuery);
|
await productHandler.handleConfirmDelete(callbackQuery);
|
||||||
}
|
}
|
||||||
// Admin user management
|
// Admin user management
|
||||||
else if (action.startsWith('view_user_')) {
|
else if (action.startsWith('view_user_')) {
|
||||||
|
|||||||
Reference in New Issue
Block a user