Merge pull request 'feature/admin-section' (#20) from feature/admin-section into main
Reviewed-on: #20
This commit was merged in pull request #20.
This commit is contained in:
BIN
corrupt-photo.jpg
Normal file
BIN
corrupt-photo.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.1 KiB |
@@ -1,6 +1,7 @@
|
|||||||
import db from '../config/database.js';
|
import db from '../config/database.js';
|
||||||
import config from '../config/config.js';
|
import config from '../config/config.js';
|
||||||
import fs from 'fs/promises';
|
import fs from 'fs/promises';
|
||||||
|
import User from "../models/User.js";
|
||||||
|
|
||||||
export default class AdminProductHandler {
|
export default class AdminProductHandler {
|
||||||
constructor(bot) {
|
constructor(bot) {
|
||||||
@@ -198,7 +199,10 @@ export default class AdminProductHandler {
|
|||||||
{
|
{
|
||||||
reply_markup: {
|
reply_markup: {
|
||||||
inline_keyboard: [[
|
inline_keyboard: [[
|
||||||
{ text: '« Back to Categories', callback_data: `prod_district_${location.country}_${location.city}_${location.district}` }
|
{
|
||||||
|
text: '« Back to Categories',
|
||||||
|
callback_data: `prod_district_${location.country}_${location.city}_${location.district}`
|
||||||
|
}
|
||||||
]]
|
]]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -259,7 +263,10 @@ export default class AdminProductHandler {
|
|||||||
}]),
|
}]),
|
||||||
[{text: '➕ Add Subcategory', callback_data: `add_subcategory_${locationId}_${categoryId}`}],
|
[{text: '➕ Add Subcategory', callback_data: `add_subcategory_${locationId}_${categoryId}`}],
|
||||||
[{text: '✏️ Edit Category', callback_data: `edit_category_${locationId}_${categoryId}`}],
|
[{text: '✏️ Edit Category', callback_data: `edit_category_${locationId}_${categoryId}`}],
|
||||||
[{ text: '« Back', callback_data: `prod_district_${location.country}_${location.city}_${location.district}` }]
|
[{
|
||||||
|
text: '« Back',
|
||||||
|
callback_data: `prod_district_${location.country}_${location.city}_${location.district}`
|
||||||
|
}]
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -297,7 +304,10 @@ export default class AdminProductHandler {
|
|||||||
{
|
{
|
||||||
reply_markup: {
|
reply_markup: {
|
||||||
inline_keyboard: [[
|
inline_keyboard: [[
|
||||||
{ text: '« Back to Subcategories', callback_data: `prod_category_${locationId}_${categoryId}` }
|
{
|
||||||
|
text: '« Back to Subcategories',
|
||||||
|
callback_data: `prod_category_${locationId}_${categoryId}`
|
||||||
|
}
|
||||||
]]
|
]]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -336,20 +346,40 @@ export default class AdminProductHandler {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleSubcategorySelection(callbackQuery) {
|
async viewProductsPage(locationId, categoryId, subcategoryId, page) {
|
||||||
const chatId = callbackQuery.message.chat.id;
|
|
||||||
const messageId = callbackQuery.message.message_id;
|
|
||||||
const [locationId, categoryId, subcategoryId] = callbackQuery.data.replace('prod_subcategory_', '').split('_');
|
|
||||||
|
|
||||||
try {
|
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(
|
const products = await db.allAsync(
|
||||||
`SELECT id, name, price, quantity_in_stock
|
`SELECT id, name, price, quantity_in_stock
|
||||||
FROM products
|
FROM products
|
||||||
WHERE location_id = ? AND category_id = ? AND subcategory_id = ?
|
WHERE location_id = ? AND category_id = ? AND subcategory_id = ?
|
||||||
ORDER BY name`,
|
ORDER BY name
|
||||||
[locationId, categoryId, subcategoryId]
|
LIMIT ?
|
||||||
|
OFFSET ?
|
||||||
|
`,
|
||||||
|
[locationId, categoryId, subcategoryId, limit, offset]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if ((products.length === 0) && (page == 0)) {
|
||||||
|
return {
|
||||||
|
text: 'No products for this location',
|
||||||
|
markup: {
|
||||||
|
inline_keyboard: [
|
||||||
|
[{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 subcategory = await db.getAsync('SELECT name FROM subcategories WHERE id = ?', [subcategoryId]);
|
||||||
const category = await db.getAsync('SELECT name FROM categories WHERE id = ?', [categoryId]);
|
const category = await db.getAsync('SELECT name FROM categories WHERE id = ?', [categoryId]);
|
||||||
|
|
||||||
@@ -359,17 +389,48 @@ export default class AdminProductHandler {
|
|||||||
text: `${prod.name} - $${prod.price} (${prod.quantity_in_stock} left)`,
|
text: `${prod.name} - $${prod.price} (${prod.quantity_in_stock} left)`,
|
||||||
callback_data: `view_product_${prod.id}`
|
callback_data: `view_product_${prod.id}`
|
||||||
}]),
|
}]),
|
||||||
[{ text: '📥 Import Products', callback_data: `add_product_${locationId}_${categoryId}_${subcategoryId}` }],
|
[{
|
||||||
[{ text: '« Back', callback_data: `prod_category_${locationId}_${categoryId}` }]
|
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('_');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const {text, markup} = await this.viewProductsPage(locationId, categoryId, subcategoryId, 0);
|
||||||
|
|
||||||
await this.bot.editMessageText(
|
await this.bot.editMessageText(
|
||||||
`📦 ${category.name} > ${subcategory.name}\nSelect product or import new ones:`,
|
text,
|
||||||
{
|
{
|
||||||
chat_id: chatId,
|
chat_id: chatId,
|
||||||
message_id: messageId,
|
message_id: messageId,
|
||||||
reply_markup: keyboard
|
reply_markup: markup
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -378,6 +439,28 @@ export default class AdminProductHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
async handleAddProduct(callbackQuery) {
|
||||||
const chatId = callbackQuery.message.chat.id;
|
const chatId = callbackQuery.message.chat.id;
|
||||||
const messageId = callbackQuery.message.message_id;
|
const messageId = callbackQuery.message.message_id;
|
||||||
@@ -421,7 +504,10 @@ export default class AdminProductHandler {
|
|||||||
parse_mode: 'HTML',
|
parse_mode: 'HTML',
|
||||||
reply_markup: {
|
reply_markup: {
|
||||||
inline_keyboard: [[
|
inline_keyboard: [[
|
||||||
{ text: '❌ Cancel', callback_data: `prod_subcategory_${locationId}_${categoryId}_${subcategoryId}` }
|
{
|
||||||
|
text: '❌ Cancel',
|
||||||
|
callback_data: `prod_subcategory_${locationId}_${categoryId}_${subcategoryId}`
|
||||||
|
}
|
||||||
]]
|
]]
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -449,8 +535,11 @@ export default class AdminProductHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const file = await this.bot.getFile(msg.document.file_id);
|
const file = await this.bot.getFile(msg.document.file_id);
|
||||||
const fileContent = await this.bot.downloadFile(file.file_id, '/tmp');
|
|
||||||
|
const fileContent = await this.bot.downloadFile(file.file_id, '.');
|
||||||
jsonContent = await fs.readFile(fileContent, 'utf8');
|
jsonContent = await fs.readFile(fileContent, 'utf8');
|
||||||
|
await fs.rm(fileContent);
|
||||||
|
|
||||||
} else if (msg.text) {
|
} else if (msg.text) {
|
||||||
jsonContent = msg.text;
|
jsonContent = msg.text;
|
||||||
} else {
|
} else {
|
||||||
@@ -495,7 +584,10 @@ export default class AdminProductHandler {
|
|||||||
{
|
{
|
||||||
reply_markup: {
|
reply_markup: {
|
||||||
inline_keyboard: [[
|
inline_keyboard: [[
|
||||||
{ text: '« Back to Products', callback_data: `prod_subcategory_${state.locationId}_${state.categoryId}_${state.subcategoryId}` }
|
{
|
||||||
|
text: '« Back to Products',
|
||||||
|
callback_data: `prod_subcategory_${state.locationId}_${state.categoryId}_${state.subcategoryId}`
|
||||||
|
}
|
||||||
]]
|
]]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -503,9 +595,9 @@ export default class AdminProductHandler {
|
|||||||
|
|
||||||
this.userStates.delete(chatId);
|
this.userStates.delete(chatId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await db.runAsync('ROLLBACK');
|
|
||||||
console.error('Error importing products:', error);
|
console.error('Error importing products:', error);
|
||||||
await this.bot.sendMessage(chatId, 'Error importing products. Please check the data and try again.');
|
await this.bot.sendMessage(chatId, 'Error importing products. Please check the data and try again.');
|
||||||
|
await db.runAsync('ROLLBACK');
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -555,26 +647,137 @@ Coordinates: ${product.hidden_coordinates}
|
|||||||
{text: '✏️ Edit', callback_data: `edit_product_${productId}`},
|
{text: '✏️ Edit', callback_data: `edit_product_${productId}`},
|
||||||
{text: '❌ Delete', callback_data: `delete_product_${productId}`}
|
{text: '❌ Delete', callback_data: `delete_product_${productId}`}
|
||||||
],
|
],
|
||||||
[{ text: '« Back', callback_data: `prod_subcategory_${product.location_id}_${product.category_id}_${product.subcategory_id}` }]
|
[{
|
||||||
|
text: '« Back',
|
||||||
|
callback_data: `prod_subcategory_${product.location_id}_${product.category_id}_${product.subcategory_id}`
|
||||||
|
}]
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
// Send product photos
|
// Send product photos
|
||||||
if (product.photo_url) {
|
if (product.photo_url) {
|
||||||
|
try {
|
||||||
await this.bot.sendPhoto(chatId, product.photo_url, {caption: 'Public photo'});
|
await this.bot.sendPhoto(chatId, product.photo_url, {caption: 'Public photo'});
|
||||||
|
} catch (e) {
|
||||||
|
await this.bot.sendPhoto(chatId, "./corrupt-photo.jpg", {caption: 'Public photo'})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (product.hidden_photo_url) {
|
if (product.hidden_photo_url) {
|
||||||
|
try {
|
||||||
await this.bot.sendPhoto(chatId, product.hidden_photo_url, {caption: 'Hidden photo'});
|
await this.bot.sendPhoto(chatId, product.hidden_photo_url, {caption: 'Hidden photo'});
|
||||||
|
} catch (e) {
|
||||||
|
await this.bot.sendPhoto(chatId, "./corrupt-photo.jpg", {caption: 'Hidden photo'})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.bot.editMessageText(message, {
|
await this.bot.deleteMessage(chatId, messageId);
|
||||||
chat_id: chatId,
|
await this.bot.sendMessage(chatId, message, {reply_markup: keyboard});
|
||||||
message_id: messageId,
|
|
||||||
reply_markup: keyboard
|
|
||||||
});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in handleViewProduct:', error);
|
console.error('Error in handleViewProduct:', error);
|
||||||
await this.bot.sendMessage(chatId, 'Error loading product details. Please try again.');
|
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.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,35 @@
|
|||||||
import db from '../config/database.js';
|
|
||||||
import User from '../models/User.js';
|
import User from '../models/User.js';
|
||||||
|
import config from "../config/config.js";
|
||||||
|
|
||||||
export default class UserHandler {
|
export default class UserHandler {
|
||||||
constructor(bot) {
|
constructor(bot) {
|
||||||
this.bot = bot;
|
this.bot = bot;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async canUseBot(msg) {
|
||||||
|
const userId = msg.from.id;
|
||||||
|
const user = await User.getById(userId);
|
||||||
|
|
||||||
|
const keyboard = {
|
||||||
|
inline_keyboard: [
|
||||||
|
[{text: "Contact support", url: config.SUPPORT_LINK}]
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (user?.status) {
|
||||||
|
case 0:
|
||||||
|
return true;
|
||||||
|
case 1:
|
||||||
|
await this.bot.sendMessage(userId, '⚠️Your account has been deleted by administrator', {reply_markup: keyboard});
|
||||||
|
return false;
|
||||||
|
case 2:
|
||||||
|
await this.bot.sendMessage(userId, '⚠️Your account has been blocked by administrator', {reply_markup: keyboard});
|
||||||
|
return false;
|
||||||
|
default:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async showProfile(msg) {
|
async showProfile(msg) {
|
||||||
const chatId = msg.chat.id;
|
const chatId = msg.chat.id;
|
||||||
const userId = msg.from.id;
|
const userId = msg.from.id;
|
||||||
|
|||||||
19
src/index.js
19
src/index.js
@@ -69,7 +69,15 @@ bot.onText(/\/admin/, async (msg) => {
|
|||||||
|
|
||||||
// Handle user menu buttons
|
// Handle user menu buttons
|
||||||
bot.on('message', async (msg) => {
|
bot.on('message', async (msg) => {
|
||||||
if (!msg.text) return;
|
if (msg.text && msg.text.toLowerCase() === '/start') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const canUse = await userHandler.canUseBot(msg);
|
||||||
|
|
||||||
|
if (!canUse) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (msg.text.toLowerCase() === '/start') {
|
if (msg.text.toLowerCase() === '/start') {
|
||||||
return;
|
return;
|
||||||
@@ -271,12 +279,21 @@ bot.on('callback_query', async (callbackQuery) => {
|
|||||||
} else if (action.startsWith('prod_subcategory_')) {
|
} else if (action.startsWith('prod_subcategory_')) {
|
||||||
logDebug(action, 'handleSubcategorySelection');
|
logDebug(action, 'handleSubcategorySelection');
|
||||||
await adminProductHandler.handleSubcategorySelection(callbackQuery);
|
await adminProductHandler.handleSubcategorySelection(callbackQuery);
|
||||||
|
} else if (action.startsWith('list_products_')) {
|
||||||
|
logDebug(action, 'handleSubcategorySelection');
|
||||||
|
await adminProductHandler.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 adminProductHandler.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 adminProductHandler.handleViewProduct(callbackQuery);
|
||||||
|
} else if (action.startsWith('delete_product_')) {
|
||||||
|
logDebug(action, 'handleViewProduct');
|
||||||
|
await adminProductHandler.handleProductDelete(callbackQuery);
|
||||||
|
} else if (action.startsWith('confirm_delete_product_')) {
|
||||||
|
logDebug(action, 'handleConfirmDelete');
|
||||||
|
await adminProductHandler.handleConfirmDelete(callbackQuery);
|
||||||
}
|
}
|
||||||
// Admin user management
|
// Admin user management
|
||||||
else if (action.startsWith('view_user_')) {
|
else if (action.startsWith('view_user_')) {
|
||||||
|
|||||||
Reference in New Issue
Block a user