969 lines
36 KiB
JavaScript
969 lines
36 KiB
JavaScript
import db from '../config/database.js';
|
||
import config from '../config/config.js';
|
||
import fs from 'fs/promises';
|
||
import User from "../models/User.js";
|
||
|
||
export default class AdminProductHandler {
|
||
constructor(bot) {
|
||
this.bot = bot;
|
||
this.userStates = new Map();
|
||
}
|
||
|
||
isAdmin(userId) {
|
||
return config.ADMIN_IDS.includes(userId.toString());
|
||
}
|
||
|
||
async handleProductManagement(msg) {
|
||
const chatId = msg.chat?.id || msg.message?.chat.id;
|
||
|
||
if (!this.isAdmin(msg.from?.id || msg.message?.from.id)) {
|
||
await this.bot.sendMessage(chatId, 'Unauthorized access.');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const countries = await db.allAsync(
|
||
'SELECT DISTINCT country FROM locations ORDER BY country'
|
||
);
|
||
|
||
if (countries.length === 0) {
|
||
await this.bot.sendMessage(
|
||
chatId,
|
||
'No locations available. Please add locations first.',
|
||
{
|
||
reply_markup: {
|
||
inline_keyboard: [[
|
||
{text: '📍 Manage Locations', callback_data: 'view_locations'}
|
||
]]
|
||
}
|
||
}
|
||
);
|
||
return;
|
||
}
|
||
|
||
const keyboard = {
|
||
inline_keyboard: countries.map(loc => [{
|
||
text: loc.country,
|
||
callback_data: `prod_country_${loc.country}`
|
||
}])
|
||
};
|
||
|
||
await this.bot.sendMessage(
|
||
chatId,
|
||
'🌍 Select country to manage products:',
|
||
{reply_markup: keyboard}
|
||
);
|
||
} catch (error) {
|
||
console.error('Error in handleProductManagement:', error);
|
||
await this.bot.sendMessage(chatId, 'Error loading locations. Please try again.');
|
||
}
|
||
}
|
||
|
||
async handleCountrySelection(callbackQuery) {
|
||
const chatId = callbackQuery.message.chat.id;
|
||
const messageId = callbackQuery.message.message_id;
|
||
const country = callbackQuery.data.replace('prod_country_', '');
|
||
|
||
try {
|
||
const cities = await db.allAsync(
|
||
'SELECT DISTINCT city FROM locations WHERE country = ? ORDER BY city',
|
||
[country]
|
||
);
|
||
|
||
const keyboard = {
|
||
inline_keyboard: [
|
||
...cities.map(loc => [{
|
||
text: loc.city,
|
||
callback_data: `prod_city_${country}_${loc.city}`
|
||
}]),
|
||
[{text: '« Back', callback_data: 'manage_products'}]
|
||
]
|
||
};
|
||
|
||
await this.bot.editMessageText(
|
||
`🏙 Select city in ${country}:`,
|
||
{
|
||
chat_id: chatId,
|
||
message_id: messageId,
|
||
reply_markup: keyboard
|
||
}
|
||
);
|
||
} catch (error) {
|
||
console.error('Error in handleCountrySelection:', error);
|
||
await this.bot.sendMessage(chatId, 'Error loading cities. Please try again.');
|
||
}
|
||
}
|
||
|
||
async handleCitySelection(callbackQuery) {
|
||
const chatId = callbackQuery.message.chat.id;
|
||
const messageId = callbackQuery.message.message_id;
|
||
const [country, city] = callbackQuery.data.replace('prod_city_', '').split('_');
|
||
|
||
try {
|
||
const districts = await db.allAsync(
|
||
'SELECT district FROM locations WHERE country = ? AND city = ? ORDER BY district',
|
||
[country, city]
|
||
);
|
||
|
||
const keyboard = {
|
||
inline_keyboard: [
|
||
...districts.map(loc => [{
|
||
text: loc.district,
|
||
callback_data: `prod_district_${country}_${city}_${loc.district}`
|
||
}]),
|
||
[{text: '« Back', callback_data: `prod_country_${country}`}]
|
||
]
|
||
};
|
||
|
||
await this.bot.editMessageText(
|
||
`📍 Select district in ${city}:`,
|
||
{
|
||
chat_id: chatId,
|
||
message_id: messageId,
|
||
reply_markup: keyboard
|
||
}
|
||
);
|
||
} catch (error) {
|
||
console.error('Error in handleCitySelection:', error);
|
||
await this.bot.sendMessage(chatId, 'Error loading districts. Please try again.');
|
||
}
|
||
}
|
||
|
||
async handleDistrictSelection(callbackQuery) {
|
||
const chatId = callbackQuery.message.chat.id;
|
||
const messageId = callbackQuery.message.message_id;
|
||
const [country, city, district] = callbackQuery.data.replace('prod_district_', '').split('_');
|
||
|
||
try {
|
||
const location = await db.getAsync(
|
||
'SELECT id FROM locations WHERE country = ? AND city = ? AND district = ?',
|
||
[country, city, district]
|
||
);
|
||
|
||
if (!location) {
|
||
throw new Error('Location not found');
|
||
}
|
||
|
||
const categories = await db.allAsync(
|
||
'SELECT id, name FROM categories WHERE location_id = ? ORDER BY name',
|
||
[location.id]
|
||
);
|
||
|
||
const keyboard = {
|
||
inline_keyboard: [
|
||
...categories.map(cat => [{
|
||
text: cat.name,
|
||
callback_data: `prod_category_${location.id}_${cat.id}`
|
||
}]),
|
||
[{text: '➕ Add Category', callback_data: `add_category_${location.id}`}],
|
||
[{text: '« Back', callback_data: `prod_city_${country}_${city}`}]
|
||
]
|
||
};
|
||
|
||
await this.bot.editMessageText(
|
||
'📦 Select or add category:',
|
||
{
|
||
chat_id: chatId,
|
||
message_id: messageId,
|
||
reply_markup: keyboard
|
||
}
|
||
);
|
||
} catch (error) {
|
||
console.error('Error in handleDistrictSelection:', error);
|
||
await this.bot.sendMessage(chatId, 'Error loading categories. Please try again.');
|
||
}
|
||
}
|
||
|
||
async handleCategoryInput(msg) {
|
||
const chatId = msg.chat.id;
|
||
const state = this.userStates.get(chatId);
|
||
|
||
if (!state || !state.action?.startsWith('add_category_')) return false;
|
||
|
||
try {
|
||
const locationId = state.action.replace('add_category_', '');
|
||
|
||
await db.runAsync(
|
||
'INSERT INTO categories (location_id, name) VALUES (?, ?)',
|
||
[locationId, msg.text]
|
||
);
|
||
|
||
const location = await db.getAsync(
|
||
'SELECT country, city, district FROM locations WHERE id = ?',
|
||
[locationId]
|
||
);
|
||
|
||
await this.bot.sendMessage(
|
||
chatId,
|
||
`✅ Category "${msg.text}" added successfully!`,
|
||
{
|
||
reply_markup: {
|
||
inline_keyboard: [[
|
||
{
|
||
text: '« Back to Categories',
|
||
callback_data: `prod_district_${location.country}_${location.city}_${location.district}`
|
||
}
|
||
]]
|
||
}
|
||
}
|
||
);
|
||
|
||
this.userStates.delete(chatId);
|
||
} catch (error) {
|
||
if (error.code === 'SQLITE_CONSTRAINT') {
|
||
await this.bot.sendMessage(chatId, 'This category already exists in this location.');
|
||
} else {
|
||
console.error('Error adding category:', error);
|
||
await this.bot.sendMessage(chatId, 'Error adding category. Please try again.');
|
||
}
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
async handleAddCategory(callbackQuery) {
|
||
const chatId = callbackQuery.message.chat.id;
|
||
const locationId = callbackQuery.data.replace('add_category_', '');
|
||
|
||
this.userStates.set(chatId, {action: `add_category_${locationId}`});
|
||
|
||
await this.bot.editMessageText(
|
||
'Please enter the name for the new category:',
|
||
{
|
||
chat_id: chatId,
|
||
message_id: callbackQuery.message.message_id,
|
||
reply_markup: {
|
||
inline_keyboard: [[
|
||
{text: '❌ Cancel', callback_data: `prod_district_${locationId}`}
|
||
]]
|
||
}
|
||
}
|
||
);
|
||
}
|
||
|
||
async handleCategorySelection(callbackQuery) {
|
||
const chatId = callbackQuery.message.chat.id;
|
||
const messageId = callbackQuery.message.message_id;
|
||
const [locationId, categoryId] = callbackQuery.data.replace('prod_category_', '').split('_');
|
||
|
||
try {
|
||
const subcategories = await db.allAsync(
|
||
'SELECT id, name FROM subcategories WHERE category_id = ? ORDER BY name',
|
||
[categoryId]
|
||
);
|
||
|
||
const category = await db.getAsync('SELECT name FROM categories WHERE id = ?', [categoryId]);
|
||
const location = await db.getAsync('SELECT country, city, district FROM locations WHERE id = ?', [locationId]);
|
||
|
||
const keyboard = {
|
||
inline_keyboard: [
|
||
...subcategories.map(sub => [{
|
||
text: sub.name,
|
||
callback_data: `prod_subcategory_${locationId}_${categoryId}_${sub.id}`
|
||
}]),
|
||
[{text: '➕ Add Subcategory', callback_data: `add_subcategory_${locationId}_${categoryId}`}],
|
||
[{text: '✏️ Edit Category', callback_data: `edit_category_${locationId}_${categoryId}`}],
|
||
[{
|
||
text: '« Back',
|
||
callback_data: `prod_district_${location.country}_${location.city}_${location.district}`
|
||
}]
|
||
]
|
||
};
|
||
|
||
await this.bot.editMessageText(
|
||
`📦 Category: ${category.name}\nSelect or add subcategory:`,
|
||
{
|
||
chat_id: chatId,
|
||
message_id: messageId,
|
||
reply_markup: keyboard
|
||
}
|
||
);
|
||
} catch (error) {
|
||
console.error('Error in handleCategorySelection:', error);
|
||
await this.bot.sendMessage(chatId, 'Error loading subcategories. Please try again.');
|
||
}
|
||
}
|
||
|
||
async handleSubcategoryInput(msg) {
|
||
const chatId = msg.chat.id;
|
||
const state = this.userStates.get(chatId);
|
||
|
||
if (!state || !state.action?.startsWith('add_subcategory_')) return false;
|
||
|
||
try {
|
||
const [locationId, categoryId] = state.action.replace('add_subcategory_', '').split('_');
|
||
|
||
await db.runAsync(
|
||
'INSERT INTO subcategories (category_id, name) VALUES (?, ?)',
|
||
[categoryId, msg.text]
|
||
);
|
||
|
||
await this.bot.sendMessage(
|
||
chatId,
|
||
`✅ Subcategory "${msg.text}" added successfully!`,
|
||
{
|
||
reply_markup: {
|
||
inline_keyboard: [[
|
||
{
|
||
text: '« Back to Subcategories',
|
||
callback_data: `prod_category_${locationId}_${categoryId}`
|
||
}
|
||
]]
|
||
}
|
||
}
|
||
);
|
||
|
||
this.userStates.delete(chatId);
|
||
} catch (error) {
|
||
if (error.code === 'SQLITE_CONSTRAINT') {
|
||
await this.bot.sendMessage(chatId, 'This subcategory already exists in this category.');
|
||
} else {
|
||
console.error('Error adding subcategory:', error);
|
||
await this.bot.sendMessage(chatId, 'Error adding subcategory. Please try again.');
|
||
}
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
async handleAddSubcategory(callbackQuery) {
|
||
const chatId = callbackQuery.message.chat.id;
|
||
const [locationId, categoryId] = callbackQuery.data.replace('add_subcategory_', '').split('_');
|
||
|
||
this.userStates.set(chatId, {action: `add_subcategory_${locationId}_${categoryId}`});
|
||
|
||
await this.bot.editMessageText(
|
||
'Please enter the name for the new subcategory:',
|
||
{
|
||
chat_id: chatId,
|
||
message_id: callbackQuery.message.message_id,
|
||
reply_markup: {
|
||
inline_keyboard: [[
|
||
{text: '❌ Cancel', callback_data: `prod_category_${locationId}_${categoryId}`}
|
||
]]
|
||
}
|
||
}
|
||
);
|
||
}
|
||
|
||
async viewProductsPage(locationId, categoryId, subcategoryId, page) {
|
||
try {
|
||
const limit = 10;
|
||
const offset = (page || 0) * limit;
|
||
|
||
const previousPage = page > 0 ? page - 1 : 0;
|
||
const nextPage = page + 1;
|
||
|
||
const products = await db.allAsync(
|
||
`SELECT id, name, price, quantity_in_stock
|
||
FROM products
|
||
WHERE location_id = ? AND category_id = ? AND subcategory_id = ?
|
||
ORDER BY name
|
||
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.'};
|
||
}
|
||
}
|
||
|
||
async handleSubcategorySelection(callbackQuery) {
|
||
const chatId = callbackQuery.message.chat.id;
|
||
const messageId = callbackQuery.message.message_id;
|
||
const [locationId, categoryId, subcategoryId] = callbackQuery.data.replace('prod_subcategory_', '').split('_');
|
||
|
||
const state = this.userStates.get(chatId)
|
||
|
||
for (const msgId of state?.msgToDelete || []) {
|
||
try {
|
||
await this.bot.deleteMessage(chatId, msgId);
|
||
} catch (e) {
|
||
// ignore if can't delete
|
||
}
|
||
}
|
||
|
||
this.userStates.delete(chatId);
|
||
|
||
try {
|
||
const {text, markup} = await this.viewProductsPage(locationId, categoryId, subcategoryId, 0);
|
||
|
||
await this.bot.editMessageText(
|
||
text,
|
||
{
|
||
chat_id: chatId,
|
||
message_id: messageId,
|
||
reply_markup: markup
|
||
}
|
||
);
|
||
} catch (error) {
|
||
console.error('Error in handleSubcategorySelection:', error);
|
||
await this.bot.sendMessage(chatId, 'Error loading products. Please try again.');
|
||
}
|
||
}
|
||
|
||
async handleProductListPage(callbackQuery) {
|
||
if (!this.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 this.bot.editMessageText(text, {
|
||
chat_id: chatId,
|
||
message_id: callbackQuery.message.message_id,
|
||
reply_markup: markup,
|
||
parse_mode: 'HTML'
|
||
});
|
||
} catch (e) {
|
||
return;
|
||
}
|
||
}
|
||
|
||
async handleAddProduct(callbackQuery) {
|
||
const chatId = callbackQuery.message.chat.id;
|
||
const messageId = callbackQuery.message.message_id;
|
||
const [locationId, categoryId, subcategoryId] = callbackQuery.data.replace('add_product_', '').split('_');
|
||
|
||
try {
|
||
const location = await db.getAsync(
|
||
'SELECT country, city, district FROM locations WHERE id = ?',
|
||
[locationId]
|
||
);
|
||
const category = await db.getAsync('SELECT name FROM categories WHERE id = ?', [categoryId]);
|
||
const subcategory = await db.getAsync('SELECT name FROM subcategories WHERE id = ?', [subcategoryId]);
|
||
|
||
const sampleProducts = [
|
||
{
|
||
name: "Sample Product 1",
|
||
price: 100,
|
||
description: "Product description",
|
||
private_data: "Hidden details about the product",
|
||
quantity_in_stock: 10,
|
||
photo_url: "https://example.com/photo.jpg",
|
||
hidden_photo_url: "https://example.com/hidden.jpg",
|
||
hidden_coordinates: "40.7128,-74.0060",
|
||
hidden_description: "Secret location details"
|
||
}
|
||
];
|
||
|
||
const jsonExample = JSON.stringify(sampleProducts, null, 2);
|
||
const message = `To import products, send a JSON file with an array of products in the following format:\n\n<pre>${jsonExample}</pre>\n\nEach product must have all the fields shown above.\n\nYou can either:\n1. Send the JSON as text\n2. Upload a .json file`;
|
||
|
||
this.userStates.set(chatId, {
|
||
action: 'import_products',
|
||
locationId,
|
||
categoryId,
|
||
subcategoryId
|
||
});
|
||
|
||
await this.bot.editMessageText(message, {
|
||
chat_id: chatId,
|
||
message_id: messageId,
|
||
parse_mode: 'HTML',
|
||
reply_markup: {
|
||
inline_keyboard: [[
|
||
{
|
||
text: '❌ Cancel',
|
||
callback_data: `prod_subcategory_${locationId}_${categoryId}_${subcategoryId}`
|
||
}
|
||
]]
|
||
}
|
||
});
|
||
} catch (error) {
|
||
console.error('Error in handleAddProduct:', error);
|
||
await this.bot.sendMessage(chatId, 'Error preparing product import. Please try again.');
|
||
}
|
||
}
|
||
|
||
async handleProductImport(msg) {
|
||
const chatId = msg.chat.id;
|
||
const state = this.userStates.get(chatId);
|
||
|
||
if (!state || state.action !== 'import_products') return false;
|
||
|
||
try {
|
||
let products;
|
||
let jsonContent;
|
||
|
||
// Handle file upload
|
||
if (msg.document) {
|
||
if (!msg.document.file_name.endsWith('.json')) {
|
||
await this.bot.sendMessage(chatId, 'Please upload a .json file.');
|
||
return true;
|
||
}
|
||
|
||
const file = await this.bot.getFile(msg.document.file_id);
|
||
|
||
const fileContent = await this.bot.downloadFile(file.file_id, '.');
|
||
jsonContent = await fs.readFile(fileContent, 'utf8');
|
||
await fs.rm(fileContent);
|
||
|
||
} else if (msg.text) {
|
||
jsonContent = msg.text;
|
||
} else {
|
||
await this.bot.sendMessage(chatId, 'Please send either a JSON file or JSON text.');
|
||
return true;
|
||
}
|
||
|
||
try {
|
||
products = JSON.parse(jsonContent);
|
||
if (!Array.isArray(products)) {
|
||
throw new Error('Input must be an array of products');
|
||
}
|
||
} catch (e) {
|
||
await this.bot.sendMessage(chatId, 'Invalid JSON format. Please check the format and try again.');
|
||
return true;
|
||
}
|
||
|
||
await db.runAsync('BEGIN TRANSACTION');
|
||
|
||
for (const product of products) {
|
||
await db.runAsync(
|
||
`INSERT INTO products (
|
||
location_id, category_id, subcategory_id,
|
||
name, price, description, private_data,
|
||
quantity_in_stock, photo_url, hidden_photo_url,
|
||
hidden_coordinates, hidden_description
|
||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||
[
|
||
state.locationId, state.categoryId, state.subcategoryId,
|
||
product.name, product.price, product.description, product.private_data,
|
||
product.quantity_in_stock, product.photo_url, product.hidden_photo_url,
|
||
product.hidden_coordinates, product.hidden_description
|
||
]
|
||
);
|
||
}
|
||
|
||
await db.runAsync('COMMIT');
|
||
|
||
await this.bot.sendMessage(
|
||
chatId,
|
||
`✅ Successfully imported ${products.length} products!`,
|
||
{
|
||
reply_markup: {
|
||
inline_keyboard: [[
|
||
{
|
||
text: '« Back to Products',
|
||
callback_data: `prod_subcategory_${state.locationId}_${state.categoryId}_${state.subcategoryId}`
|
||
}
|
||
]]
|
||
}
|
||
}
|
||
);
|
||
|
||
this.userStates.delete(chatId);
|
||
} catch (error) {
|
||
console.error('Error importing products:', error);
|
||
await this.bot.sendMessage(chatId, 'Error importing products. Please check the data and try again.');
|
||
await db.runAsync('ROLLBACK');
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
async handleProductEditImport(msg) {
|
||
const chatId = msg.chat.id;
|
||
const state = this.userStates.get(chatId);
|
||
|
||
if (!state || state.action !== 'edit_product') return false;
|
||
|
||
try {
|
||
let product;
|
||
let jsonContent;
|
||
|
||
// Handle file upload
|
||
if (msg.document) {
|
||
if (!msg.document.file_name.endsWith('.json')) {
|
||
await this.bot.sendMessage(chatId, 'Please upload a .json file.');
|
||
return true;
|
||
}
|
||
|
||
const file = await this.bot.getFile(msg.document.file_id);
|
||
|
||
const fileContent = await this.bot.downloadFile(file.file_id, '.');
|
||
jsonContent = await fs.readFile(fileContent, 'utf8');
|
||
await fs.rm(fileContent);
|
||
|
||
} else if (msg.text) {
|
||
jsonContent = msg.text;
|
||
} else {
|
||
await this.bot.sendMessage(chatId, 'Please send either a JSON file or JSON text.');
|
||
return true;
|
||
}
|
||
|
||
try {
|
||
product = JSON.parse(jsonContent);
|
||
} catch (e) {
|
||
await this.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 = ?
|
||
`,
|
||
[
|
||
state.locationId, state.categoryId, state.subcategoryId,
|
||
product.name, product.price, product.description, product.private_data,
|
||
product.quantity_in_stock, product.photo_url, product.hidden_photo_url,
|
||
product.hidden_coordinates, product.hidden_description, state.productId
|
||
]
|
||
);
|
||
|
||
await db.runAsync('COMMIT');
|
||
|
||
await this.bot.sendMessage(
|
||
chatId,
|
||
`✅ Successfully edited!`,
|
||
{
|
||
reply_markup: {
|
||
inline_keyboard: [[
|
||
{
|
||
text: '« Back to Products',
|
||
callback_data: `prod_subcategory_${state.locationId}_${state.categoryId}_${state.subcategoryId}`
|
||
}
|
||
]]
|
||
}
|
||
}
|
||
);
|
||
|
||
this.userStates.delete(chatId);
|
||
} catch (error) {
|
||
console.error('Error importing products:', error);
|
||
await this.bot.sendMessage(chatId, 'Error importing products. Please check the data and try again.');
|
||
await db.runAsync('ROLLBACK');
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
async handleViewProduct(callbackQuery) {
|
||
const chatId = callbackQuery.message.chat.id;
|
||
const messageId = callbackQuery.message.message_id;
|
||
const productId = callbackQuery.data.replace('view_product_', '');
|
||
|
||
try {
|
||
const product = await db.getAsync(
|
||
`SELECT p.*, c.name as category_name, s.name as subcategory_name,
|
||
l.country, l.city, l.district
|
||
FROM products p
|
||
JOIN categories c ON p.category_id = c.id
|
||
JOIN subcategories s ON p.subcategory_id = s.id
|
||
JOIN locations l ON p.location_id = l.id
|
||
WHERE p.id = ?`,
|
||
[productId]
|
||
);
|
||
|
||
if (!product) {
|
||
throw new Error('Product not found');
|
||
}
|
||
|
||
const message = `
|
||
📦 Product Details:
|
||
|
||
Name: ${product.name}
|
||
Price: $${product.price}
|
||
Description: ${product.description}
|
||
Stock: ${product.quantity_in_stock}
|
||
Location: ${product.country}, ${product.city}, ${product.district}
|
||
Category: ${product.category_name}
|
||
Subcategory: ${product.subcategory_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_subcategory_${product.location_id}_${product.category_id}_${product.subcategory_id}`
|
||
}]
|
||
]
|
||
};
|
||
|
||
let photoMessage;
|
||
let hiddenPhotoMessage;
|
||
|
||
// Send product photos
|
||
if (product.photo_url) {
|
||
try {
|
||
photoMessage = await this.bot.sendPhoto(chatId, product.photo_url, {caption: 'Public photo'});
|
||
} catch (e) {
|
||
photoMessage = await this.bot.sendPhoto(chatId, "./corrupt-photo.jpg", {caption: 'Public photo'})
|
||
}
|
||
}
|
||
if (product.hidden_photo_url) {
|
||
try {
|
||
hiddenPhotoMessage = await this.bot.sendPhoto(chatId, product.hidden_photo_url, {caption: 'Hidden photo'});
|
||
} catch (e) {
|
||
hiddenPhotoMessage = await this.bot.sendPhoto(chatId, "./corrupt-photo.jpg", {caption: 'Hidden photo'})
|
||
}
|
||
}
|
||
|
||
this.userStates.set(chatId, {
|
||
msgToDelete: [photoMessage.message_id, hiddenPhotoMessage.message_id]
|
||
})
|
||
|
||
await this.bot.deleteMessage(chatId, messageId);
|
||
await this.bot.sendMessage(chatId, message, {reply_markup: keyboard});
|
||
} catch (error) {
|
||
console.error('Error in handleViewProduct:', error);
|
||
await this.bot.sendMessage(chatId, 'Error loading product details. Please try again.');
|
||
}
|
||
}
|
||
|
||
async handleProductEdit(callbackQuery) {
|
||
const chatId = callbackQuery.message.chat.id;
|
||
const messageId = callbackQuery.message.message_id;
|
||
const productId = callbackQuery.data.replace('edit_product_', '');
|
||
|
||
try {
|
||
const product = await db.getAsync(
|
||
`SELECT p.*, c.name as category_name, s.name as subcategory_name,
|
||
l.country, l.city, l.district
|
||
FROM products p
|
||
JOIN categories c ON p.category_id = c.id
|
||
JOIN subcategories s ON p.subcategory_id = s.id
|
||
JOIN locations l ON p.location_id = l.id
|
||
WHERE p.id = ?`,
|
||
[productId]
|
||
);
|
||
|
||
if (!product) {
|
||
throw new Error('Product not found');
|
||
}
|
||
|
||
const locationId = product.location_id;
|
||
const categoryId = product.category_id;
|
||
const subcategoryId = product.subcategory_id;
|
||
|
||
const sampleProduct = {
|
||
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(sampleProduct, null, 2);
|
||
const message = `To edit 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`;
|
||
|
||
this.userStates.set(chatId, {
|
||
action: 'edit_product',
|
||
locationId,
|
||
categoryId,
|
||
subcategoryId,
|
||
productId
|
||
});
|
||
|
||
await this.bot.editMessageText(message, {
|
||
chat_id: chatId,
|
||
message_id: messageId,
|
||
parse_mode: 'HTML',
|
||
reply_markup: {
|
||
inline_keyboard: [[
|
||
{
|
||
text: '❌ Cancel',
|
||
callback_data: `prod_subcategory_${locationId}_${categoryId}_${subcategoryId}`
|
||
}
|
||
]]
|
||
}
|
||
});
|
||
} catch (error) {
|
||
console.error('Error in handleViewProduct:', error);
|
||
await this.bot.sendMessage(chatId, 'Error loading product details. Please try again.');
|
||
}
|
||
}
|
||
|
||
async handleProductDelete(callbackQuery) {
|
||
if (!this.isAdmin(callbackQuery.from.id)) return;
|
||
|
||
const productId = callbackQuery.data.replace('delete_product_', '');
|
||
const chatId = callbackQuery.message.chat.id;
|
||
|
||
try {
|
||
const product = await db.getAsync(
|
||
`SELECT p.*, c.name as category_name, s.name as subcategory_name,
|
||
l.country, l.city, l.district
|
||
FROM products p
|
||
JOIN categories c ON p.category_id = c.id
|
||
JOIN subcategories s ON p.subcategory_id = s.id
|
||
JOIN locations l ON p.location_id = l.id
|
||
WHERE p.id = ?`,
|
||
[productId]
|
||
);
|
||
|
||
if (!product) {
|
||
throw new Error('Product not found');
|
||
}
|
||
|
||
const keyboard = {
|
||
inline_keyboard: [
|
||
[
|
||
{text: '✅ Confirm Delete', callback_data: `confirm_delete_product_${productId}`},
|
||
{
|
||
text: '❌ Cancel',
|
||
callback_data: `prod_subcategory_${product.location_id}_${product.category_id}_${product.subcategory_id}`
|
||
}
|
||
]
|
||
]
|
||
};
|
||
|
||
await this.bot.editMessageText(
|
||
`⚠️ Are you sure you want to delete product\n\nThis action cannot be undone!`,
|
||
{
|
||
chat_id: chatId,
|
||
message_id: callbackQuery.message.message_id,
|
||
reply_markup: keyboard,
|
||
parse_mode: 'HTML'
|
||
}
|
||
);
|
||
} catch (error) {
|
||
console.error('Error in handleDeleteUser:', error);
|
||
await this.bot.sendMessage(chatId, 'Error processing delete request. Please try again.');
|
||
}
|
||
}
|
||
|
||
async handleConfirmDelete(callbackQuery) {
|
||
if (!this.isAdmin(callbackQuery.from.id)) return;
|
||
|
||
const productId = callbackQuery.data.replace('confirm_delete_product_', '');
|
||
const chatId = callbackQuery.message.chat.id;
|
||
|
||
try {
|
||
const product = await db.getAsync(
|
||
`SELECT p.*, c.name as category_name, s.name as subcategory_name,
|
||
l.country, l.city, l.district
|
||
FROM products p
|
||
JOIN categories c ON p.category_id = c.id
|
||
JOIN subcategories s ON p.subcategory_id = s.id
|
||
JOIN locations l ON p.location_id = l.id
|
||
WHERE p.id = ?`,
|
||
[productId]
|
||
);
|
||
|
||
if (!product) {
|
||
throw new Error('Product not found');
|
||
}
|
||
|
||
|
||
try {
|
||
await db.runAsync('BEGIN TRANSACTION');
|
||
await db.runAsync('DELETE FROM products WHERE id=?', [productId.toString()]);
|
||
await db.runAsync('COMMIT');
|
||
} catch (e) {
|
||
await db.runAsync("ROLLBACK");
|
||
console.error('Error deleting product:', error);
|
||
throw error;
|
||
}
|
||
|
||
const keyboard = {
|
||
inline_keyboard: [
|
||
[{
|
||
text: '« Back',
|
||
callback_data: `prod_subcategory_${product.location_id}_${product.category_id}_${product.subcategory_id}`
|
||
}]
|
||
]
|
||
};
|
||
|
||
await this.bot.editMessageText(
|
||
`✅ Product has been successfully deleted.`,
|
||
{
|
||
chat_id: chatId,
|
||
message_id: callbackQuery.message.message_id,
|
||
reply_markup: keyboard
|
||
}
|
||
);
|
||
} catch (error) {
|
||
console.error('Error in handleConfirmDelete:', error);
|
||
await this.bot.sendMessage(chatId, 'Error deleting product. Please try again.');
|
||
}
|
||
}
|
||
} |