refactoring
This commit is contained in:
17
src/context/bot.js
Normal file
17
src/context/bot.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import TelegramBot from "node-telegram-bot-api";
|
||||||
|
import config from "../config/config.js";
|
||||||
|
|
||||||
|
const initBot = () => {
|
||||||
|
try {
|
||||||
|
const bot = new TelegramBot(config.BOT_TOKEN, {polling: true});
|
||||||
|
console.log('Bot initialized successfully');
|
||||||
|
return bot;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to initialize bot:', error);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const bot = initBot();
|
||||||
|
|
||||||
|
export default bot;
|
||||||
2
src/context/userStates.js
Normal file
2
src/context/userStates.js
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
const userStates = new Map();
|
||||||
|
export default userStates;
|
||||||
@@ -1,22 +1,24 @@
|
|||||||
import config from '../config/config.js';
|
import config from '../../config/config.js';
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import db from "../config/database.js";
|
import db from "../../config/database.js";
|
||||||
import archiver from "archiver";
|
import archiver from "archiver";
|
||||||
import decompress from "decompress";
|
import decompress from "decompress";
|
||||||
|
import bot from "../../context/bot.js";
|
||||||
|
import userStates from "../../context/userStates.js";
|
||||||
|
|
||||||
export default class AdminDumpHandler {
|
export default class AdminDumpHandler {
|
||||||
constructor(bot) {
|
static isAdmin(userId) {
|
||||||
this.bot = bot;
|
|
||||||
this.userStates = new Map();
|
|
||||||
}
|
|
||||||
|
|
||||||
isAdmin(userId) {
|
|
||||||
return config.ADMIN_IDS.includes(userId.toString());
|
return config.ADMIN_IDS.includes(userId.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleDump(msg) {
|
static async handleDump(msg) {
|
||||||
const chatId = msg.chat.id;
|
const chatId = msg.chat.id;
|
||||||
|
|
||||||
|
if (!this.isAdmin(msg.from.id)) {
|
||||||
|
await bot.sendMessage(chatId, 'Unauthorized access.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const keyboard = {
|
const keyboard = {
|
||||||
inline_keyboard: [
|
inline_keyboard: [
|
||||||
[
|
[
|
||||||
@@ -27,10 +29,14 @@ export default class AdminDumpHandler {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.bot.sendMessage(chatId, 'Choose an option', {reply_markup: keyboard});
|
await bot.sendMessage(chatId, 'Choose an option', {reply_markup: keyboard});
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleExportDatabase(callbackQuery) {
|
static async handleExportDatabase(callbackQuery) {
|
||||||
|
if (!this.isAdmin(callbackQuery.from.id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const chatId = callbackQuery.message.chat.id;
|
const chatId = callbackQuery.message.chat.id;
|
||||||
|
|
||||||
const tables = [
|
const tables = [
|
||||||
@@ -69,16 +75,20 @@ export default class AdminDumpHandler {
|
|||||||
await archive.finalize();
|
await archive.finalize();
|
||||||
|
|
||||||
output.on('close', () => {
|
output.on('close', () => {
|
||||||
this.bot.sendDocument(chatId, './dump.zip', {caption: 'Database dump'});
|
bot.sendDocument(chatId, './dump.zip', {caption: 'Database dump'});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleImportDatabase(callbackQuery) {
|
static async handleImportDatabase(callbackQuery) {
|
||||||
|
if (!this.isAdmin(callbackQuery.from.id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const chatId = callbackQuery.message.chat.id;
|
const chatId = callbackQuery.message.chat.id;
|
||||||
|
|
||||||
this.userStates.set(chatId, { action: 'upload_database_dump' });
|
userStates.set(chatId, { action: 'upload_database_dump' });
|
||||||
|
|
||||||
await this.bot.editMessageText(
|
await bot.editMessageText(
|
||||||
'Please upload database dump',
|
'Please upload database dump',
|
||||||
{
|
{
|
||||||
chat_id: chatId,
|
chat_id: chatId,
|
||||||
@@ -87,7 +97,7 @@ export default class AdminDumpHandler {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getDumpStatistic() {
|
static async getDumpStatistic() {
|
||||||
const tables = [
|
const tables = [
|
||||||
"categories",
|
"categories",
|
||||||
"crypto_wallets",
|
"crypto_wallets",
|
||||||
@@ -109,36 +119,43 @@ export default class AdminDumpHandler {
|
|||||||
return stat;
|
return stat;
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleDumpImport(msg) {
|
static async handleDumpImport(msg) {
|
||||||
const chatId = msg.chat.id;
|
const chatId = msg.chat.id;
|
||||||
const state = this.userStates.get(chatId);
|
|
||||||
|
const state = userStates.get(chatId);
|
||||||
|
|
||||||
if (!state || state.action !== 'upload_database_dump') {
|
if (!state || state.action !== 'upload_database_dump') {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!this.isAdmin(msg.from.id)) {
|
||||||
|
await bot.sendMessage(chatId, 'Unauthorized access.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (msg.document) {
|
if (msg.document) {
|
||||||
if (!msg.document.file_name.endsWith('.zip')) {
|
if (!msg.document.file_name.endsWith('.zip')) {
|
||||||
await this.bot.sendMessage(chatId, 'Please upload a .zip file.');
|
await bot.sendMessage(chatId, 'Please upload a .zip file.');
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const file = await this.bot.getFile(msg.document.file_id);
|
const file = await bot.getFile(msg.document.file_id);
|
||||||
const fileContent = await this.bot.downloadFile(file.file_id, '.');
|
const fileContent = await bot.downloadFile(file.file_id, '.');
|
||||||
|
|
||||||
await decompress(fileContent, './dump');
|
await decompress(fileContent, './dump');
|
||||||
|
|
||||||
const statistics = await this.getDumpStatistic();
|
const statistics = await this.getDumpStatistic();
|
||||||
await this.bot.sendMessage(chatId, JSON.stringify(statistics, null, 2));
|
await bot.sendMessage(chatId, JSON.stringify(statistics, null, 2));
|
||||||
this.userStates.delete(chatId);
|
userStates.delete(chatId);
|
||||||
} else {
|
} else {
|
||||||
await this.bot.sendMessage(chatId, 'Please upload a valid .zip file.');
|
await bot.sendMessage(chatId, 'Please upload a valid .zip file.');
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async confirmImport(callbackQuery) {
|
static async confirmImport(callbackQuery) {
|
||||||
|
if (!this.isAdmin(callbackQuery.from.id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,19 +1,16 @@
|
|||||||
import config from '../config/config.js';
|
import config from '../../config/config.js';
|
||||||
|
import bot from "../../context/bot.js";
|
||||||
|
|
||||||
export default class AdminHandler {
|
export default class AdminHandler {
|
||||||
constructor(bot) {
|
static isAdmin(userId) {
|
||||||
this.bot = bot;
|
|
||||||
}
|
|
||||||
|
|
||||||
isAdmin(userId) {
|
|
||||||
return config.ADMIN_IDS.includes(userId.toString());
|
return config.ADMIN_IDS.includes(userId.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleAdminCommand(msg) {
|
static async handleAdminCommand(msg) {
|
||||||
const chatId = msg.chat.id;
|
const chatId = msg.chat.id;
|
||||||
|
|
||||||
if (!this.isAdmin(msg.from.id)) {
|
if (!this.isAdmin(msg.from.id)) {
|
||||||
await this.bot.sendMessage(chatId, 'Unauthorized access.');
|
await bot.sendMessage(chatId, 'Unauthorized access.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,6 +25,6 @@ export default class AdminHandler {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
await this.bot.sendMessage(chatId, 'Admin Panel:', keyboard);
|
await bot.sendMessage(chatId, 'Admin Panel:', keyboard);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,23 +1,24 @@
|
|||||||
import db from '../config/database.js';
|
import db from '../../config/database.js';
|
||||||
import Validators from '../utils/validators.js';
|
import Validators from '../../utils/validators.js';
|
||||||
import config from '../config/config.js';
|
import config from '../../config/config.js';
|
||||||
|
import userStates from "../../context/userStates.js";
|
||||||
|
import bot from "../../context/bot.js";
|
||||||
|
|
||||||
export default class AdminLocationHandler {
|
export default class AdminLocationHandler {
|
||||||
constructor(bot) {
|
static isAdmin(userId) {
|
||||||
this.bot = bot;
|
|
||||||
this.userStates = new Map();
|
|
||||||
}
|
|
||||||
|
|
||||||
isAdmin(userId) {
|
|
||||||
return config.ADMIN_IDS.includes(userId.toString());
|
return config.ADMIN_IDS.includes(userId.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleAddLocation(callbackQuery) {
|
static async handleAddLocation(callbackQuery) {
|
||||||
|
if (!this.isAdmin(callbackQuery.from.id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const chatId = callbackQuery.message.chat.id;
|
const chatId = callbackQuery.message.chat.id;
|
||||||
|
|
||||||
this.userStates.set(chatId, { action: 'add_location' });
|
userStates.set(chatId, { action: 'add_location' });
|
||||||
|
|
||||||
await this.bot.editMessageText(
|
await bot.editMessageText(
|
||||||
'Please enter the location in the following format:\nCountry|City|District',
|
'Please enter the location in the following format:\nCountry|City|District',
|
||||||
{
|
{
|
||||||
chat_id: chatId,
|
chat_id: chatId,
|
||||||
@@ -29,15 +30,22 @@ export default class AdminLocationHandler {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleLocationInput(msg) {
|
static async handleLocationInput(msg) {
|
||||||
const chatId = msg.chat.id;
|
const chatId = msg.chat.id;
|
||||||
const state = this.userStates.get(chatId);
|
const state = userStates.get(chatId);
|
||||||
|
|
||||||
if (!state || state.action !== 'add_location') return false;
|
if (!state || state.action !== 'add_location') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.isAdmin(msg.from.id)) {
|
||||||
|
await bot.sendMessage(chatId, 'Unauthorized access.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const parts = msg.text.split('|').map(s => s.trim());
|
const parts = msg.text.split('|').map(s => s.trim());
|
||||||
if (parts.length !== 3) {
|
if (parts.length !== 3) {
|
||||||
await this.bot.sendMessage(
|
await bot.sendMessage(
|
||||||
chatId,
|
chatId,
|
||||||
'Invalid format. Please use: Country|City|District'
|
'Invalid format. Please use: Country|City|District'
|
||||||
);
|
);
|
||||||
@@ -47,7 +55,7 @@ export default class AdminLocationHandler {
|
|||||||
const [country, city, district] = parts;
|
const [country, city, district] = parts;
|
||||||
|
|
||||||
if (!Validators.isValidLocation(country, city, district)) {
|
if (!Validators.isValidLocation(country, city, district)) {
|
||||||
await this.bot.sendMessage(
|
await bot.sendMessage(
|
||||||
chatId,
|
chatId,
|
||||||
'Invalid location data. All fields are required.'
|
'Invalid location data. All fields are required.'
|
||||||
);
|
);
|
||||||
@@ -65,7 +73,7 @@ export default class AdminLocationHandler {
|
|||||||
await db.runAsync('COMMIT');
|
await db.runAsync('COMMIT');
|
||||||
|
|
||||||
if (result.changes > 0) {
|
if (result.changes > 0) {
|
||||||
await this.bot.sendMessage(
|
await bot.sendMessage(
|
||||||
chatId,
|
chatId,
|
||||||
`✅ Location added successfully!\n\nCountry: ${country}\nCity: ${city}\nDistrict: ${district}`,
|
`✅ Location added successfully!\n\nCountry: ${country}\nCity: ${city}\nDistrict: ${district}`,
|
||||||
{
|
{
|
||||||
@@ -81,12 +89,12 @@ export default class AdminLocationHandler {
|
|||||||
throw new Error('Failed to insert location');
|
throw new Error('Failed to insert location');
|
||||||
}
|
}
|
||||||
|
|
||||||
this.userStates.delete(chatId);
|
userStates.delete(chatId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await db.runAsync('ROLLBACK');
|
await db.runAsync('ROLLBACK');
|
||||||
|
|
||||||
if (error.code === 'SQLITE_CONSTRAINT') {
|
if (error.code === 'SQLITE_CONSTRAINT') {
|
||||||
await this.bot.sendMessage(
|
await bot.sendMessage(
|
||||||
chatId,
|
chatId,
|
||||||
'❌ This location already exists.',
|
'❌ This location already exists.',
|
||||||
{
|
{
|
||||||
@@ -99,7 +107,7 @@ export default class AdminLocationHandler {
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
console.error('Error adding location:', error);
|
console.error('Error adding location:', error);
|
||||||
await this.bot.sendMessage(
|
await bot.sendMessage(
|
||||||
chatId,
|
chatId,
|
||||||
'❌ Error adding location. Please try again.',
|
'❌ Error adding location. Please try again.',
|
||||||
{
|
{
|
||||||
@@ -116,16 +124,16 @@ export default class AdminLocationHandler {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleViewLocations(msg) {
|
static async handleViewLocations(msg) {
|
||||||
const chatId = msg.chat?.id || msg.message?.chat.id;
|
const chatId = msg.chat?.id || msg.message?.chat.id;
|
||||||
const messageId = msg.message?.message_id;
|
const messageId = msg.message?.message_id;
|
||||||
|
|
||||||
if (!this.isAdmin(msg.from?.id || msg.message?.from.id)) {
|
if (!this.isAdmin(msg.from?.id || msg.message?.from.id)) {
|
||||||
await this.bot.sendMessage(chatId, 'Unauthorized access.');
|
await bot.sendMessage(chatId, 'Unauthorized access.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.userStates.delete(chatId);
|
userStates.delete(chatId);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const locations = await db.allAsync(`
|
const locations = await db.allAsync(`
|
||||||
@@ -146,13 +154,13 @@ export default class AdminLocationHandler {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (messageId) {
|
if (messageId) {
|
||||||
await this.bot.editMessageText(message, {
|
await bot.editMessageText(message, {
|
||||||
chat_id: chatId,
|
chat_id: chatId,
|
||||||
message_id: messageId,
|
message_id: messageId,
|
||||||
reply_markup: keyboard
|
reply_markup: keyboard
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
await this.bot.sendMessage(chatId, message, { reply_markup: keyboard });
|
await bot.sendMessage(chatId, message, { reply_markup: keyboard });
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -179,25 +187,29 @@ export default class AdminLocationHandler {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (messageId) {
|
if (messageId) {
|
||||||
await this.bot.editMessageText(message, {
|
await bot.editMessageText(message, {
|
||||||
chat_id: chatId,
|
chat_id: chatId,
|
||||||
message_id: messageId,
|
message_id: messageId,
|
||||||
reply_markup: keyboard,
|
reply_markup: keyboard,
|
||||||
parse_mode: 'Markdown'
|
parse_mode: 'Markdown'
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
await this.bot.sendMessage(chatId, message, {
|
await bot.sendMessage(chatId, message, {
|
||||||
reply_markup: keyboard,
|
reply_markup: keyboard,
|
||||||
parse_mode: 'Markdown'
|
parse_mode: 'Markdown'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error viewing locations:', error);
|
console.error('Error viewing locations:', error);
|
||||||
await this.bot.sendMessage(chatId, 'Error loading locations. Please try again.');
|
await bot.sendMessage(chatId, 'Error loading locations. Please try again.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleDeleteLocation(callbackQuery) {
|
static async handleDeleteLocation(callbackQuery) {
|
||||||
|
if (!this.isAdmin(callbackQuery.from.id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const chatId = callbackQuery.message.chat.id;
|
const chatId = callbackQuery.message.chat.id;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -221,7 +233,7 @@ export default class AdminLocationHandler {
|
|||||||
|
|
||||||
keyboard.inline_keyboard.push([{ text: '« Back', callback_data: 'view_locations' }]);
|
keyboard.inline_keyboard.push([{ text: '« Back', callback_data: 'view_locations' }]);
|
||||||
|
|
||||||
await this.bot.editMessageText(
|
await bot.editMessageText(
|
||||||
'❌ Select location to delete:\n\n*Note:* Deleting a location will also remove all associated products and categories!',
|
'❌ Select location to delete:\n\n*Note:* Deleting a location will also remove all associated products and categories!',
|
||||||
{
|
{
|
||||||
chat_id: chatId,
|
chat_id: chatId,
|
||||||
@@ -232,11 +244,15 @@ export default class AdminLocationHandler {
|
|||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in handleDeleteLocation:', error);
|
console.error('Error in handleDeleteLocation:', error);
|
||||||
await this.bot.sendMessage(chatId, 'Error loading locations. Please try again.');
|
await bot.sendMessage(chatId, 'Error loading locations. Please try again.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleConfirmDelete(callbackQuery) {
|
static async handleConfirmDelete(callbackQuery) {
|
||||||
|
if (!this.isAdmin(callbackQuery.from.id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const chatId = callbackQuery.message.chat.id;
|
const chatId = callbackQuery.message.chat.id;
|
||||||
const [country, city, district] = callbackQuery.data
|
const [country, city, district] = callbackQuery.data
|
||||||
.replace('confirm_delete_location_', '')
|
.replace('confirm_delete_location_', '')
|
||||||
@@ -253,7 +269,7 @@ export default class AdminLocationHandler {
|
|||||||
await db.runAsync('COMMIT');
|
await db.runAsync('COMMIT');
|
||||||
|
|
||||||
if (result.changes > 0) {
|
if (result.changes > 0) {
|
||||||
await this.bot.editMessageText(
|
await bot.editMessageText(
|
||||||
`✅ Location deleted successfully!\n\nCountry: ${country}\nCity: ${city}\nDistrict: ${district}`,
|
`✅ Location deleted successfully!\n\nCountry: ${country}\nCity: ${city}\nDistrict: ${district}`,
|
||||||
{
|
{
|
||||||
chat_id: chatId,
|
chat_id: chatId,
|
||||||
@@ -269,7 +285,7 @@ export default class AdminLocationHandler {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
await db.runAsync('ROLLBACK');
|
await db.runAsync('ROLLBACK');
|
||||||
console.error('Error deleting location:', error);
|
console.error('Error deleting location:', error);
|
||||||
await this.bot.sendMessage(
|
await bot.sendMessage(
|
||||||
chatId,
|
chatId,
|
||||||
'❌ Error deleting location. Please try again.',
|
'❌ Error deleting location. Please try again.',
|
||||||
{
|
{
|
||||||
@@ -281,8 +297,10 @@ export default class AdminLocationHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async backToMenu(callbackQuery) {
|
static async backToMenu(callbackQuery) {
|
||||||
if (!this.isAdmin(callbackQuery.from.id)) return;
|
if (!this.isAdmin(callbackQuery.from.id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const chatId = callbackQuery.message.chat.id;
|
const chatId = callbackQuery.message.chat.id;
|
||||||
const messageId = callbackQuery.message.message_id;
|
const messageId = callbackQuery.message.message_id;
|
||||||
@@ -298,7 +316,7 @@ export default class AdminLocationHandler {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
await this.bot.editMessageText(
|
await bot.editMessageText(
|
||||||
`You we're returned to the admin menu`,
|
`You we're returned to the admin menu`,
|
||||||
{
|
{
|
||||||
chat_id: chatId,
|
chat_id: chatId,
|
||||||
@@ -307,6 +325,6 @@ export default class AdminLocationHandler {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
this.userStates.delete(chatId);
|
userStates.delete(chatId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,33 +1,30 @@
|
|||||||
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";
|
import LocationService from "../../services/locationService.js";
|
||||||
|
import bot from "../../context/bot.js";
|
||||||
|
import CategoryService from "../../services/categoryService.js";
|
||||||
|
import userStates from "../../context/userStates.js";
|
||||||
|
import ProductService from "../../services/productService.js";
|
||||||
|
|
||||||
export default class AdminProductHandler {
|
export default class AdminProductHandler {
|
||||||
constructor(bot) {
|
static isAdmin(userId) {
|
||||||
this.bot = bot;
|
|
||||||
this.userStates = new Map();
|
|
||||||
}
|
|
||||||
|
|
||||||
isAdmin(userId) {
|
|
||||||
return config.ADMIN_IDS.includes(userId.toString());
|
return config.ADMIN_IDS.includes(userId.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleProductManagement(msg) {
|
static async handleProductManagement(msg) {
|
||||||
const chatId = msg.chat?.id || msg.message?.chat.id;
|
const chatId = msg.chat?.id || msg.message?.chat.id;
|
||||||
|
|
||||||
if (!this.isAdmin(msg.from?.id || msg.message?.from.id)) {
|
if (!this.isAdmin(msg.from?.id || msg.message?.from.id)) {
|
||||||
await this.bot.sendMessage(chatId, 'Unauthorized access.');
|
await bot.sendMessage(chatId, 'Unauthorized access.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const countries = await db.allAsync(
|
const countries = await LocationService.getCountries()
|
||||||
'SELECT DISTINCT country FROM locations ORDER BY country'
|
|
||||||
);
|
|
||||||
|
|
||||||
if (countries.length === 0) {
|
if (countries.length === 0) {
|
||||||
await this.bot.sendMessage(
|
await bot.sendMessage(
|
||||||
chatId,
|
chatId,
|
||||||
'No locations available. Please add locations first.',
|
'No locations available. Please add locations first.',
|
||||||
{
|
{
|
||||||
@@ -48,27 +45,28 @@ export default class AdminProductHandler {
|
|||||||
}])
|
}])
|
||||||
};
|
};
|
||||||
|
|
||||||
await this.bot.sendMessage(
|
await bot.sendMessage(
|
||||||
chatId,
|
chatId,
|
||||||
'🌍 Select country to manage products:',
|
'🌍 Select country to manage products:',
|
||||||
{reply_markup: keyboard}
|
{reply_markup: keyboard}
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in handleProductManagement:', error);
|
console.error('Error in handleProductManagement:', error);
|
||||||
await this.bot.sendMessage(chatId, 'Error loading locations. Please try again.');
|
await bot.sendMessage(chatId, 'Error loading locations. Please try again.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleCountrySelection(callbackQuery) {
|
static async handleCountrySelection(callbackQuery) {
|
||||||
|
if (!this.isAdmin(callbackQuery.from.id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const chatId = callbackQuery.message.chat.id;
|
const chatId = callbackQuery.message.chat.id;
|
||||||
const messageId = callbackQuery.message.message_id;
|
const messageId = callbackQuery.message.message_id;
|
||||||
const country = callbackQuery.data.replace('prod_country_', '');
|
const country = callbackQuery.data.replace('prod_country_', '');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const cities = await db.allAsync(
|
const cities = await LocationService.getCitiesByCountry(country)
|
||||||
'SELECT DISTINCT city FROM locations WHERE country = ? ORDER BY city',
|
|
||||||
[country]
|
|
||||||
);
|
|
||||||
|
|
||||||
const keyboard = {
|
const keyboard = {
|
||||||
inline_keyboard: [
|
inline_keyboard: [
|
||||||
@@ -80,7 +78,7 @@ export default class AdminProductHandler {
|
|||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
await this.bot.editMessageText(
|
await bot.editMessageText(
|
||||||
`🏙 Select city in ${country}:`,
|
`🏙 Select city in ${country}:`,
|
||||||
{
|
{
|
||||||
chat_id: chatId,
|
chat_id: chatId,
|
||||||
@@ -90,20 +88,21 @@ export default class AdminProductHandler {
|
|||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in handleCountrySelection:', error);
|
console.error('Error in handleCountrySelection:', error);
|
||||||
await this.bot.sendMessage(chatId, 'Error loading cities. Please try again.');
|
await bot.sendMessage(chatId, 'Error loading cities. Please try again.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleCitySelection(callbackQuery) {
|
static async handleCitySelection(callbackQuery) {
|
||||||
|
if (!this.isAdmin(callbackQuery.from.id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const chatId = callbackQuery.message.chat.id;
|
const chatId = callbackQuery.message.chat.id;
|
||||||
const messageId = callbackQuery.message.message_id;
|
const messageId = callbackQuery.message.message_id;
|
||||||
const [country, city] = callbackQuery.data.replace('prod_city_', '').split('_');
|
const [country, city] = callbackQuery.data.replace('prod_city_', '').split('_');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const districts = await db.allAsync(
|
const districts = await LocationService.getDistrictsByCountryAndCity(country, city);
|
||||||
'SELECT district FROM locations WHERE country = ? AND city = ? ORDER BY district',
|
|
||||||
[country, city]
|
|
||||||
);
|
|
||||||
|
|
||||||
const keyboard = {
|
const keyboard = {
|
||||||
inline_keyboard: [
|
inline_keyboard: [
|
||||||
@@ -115,7 +114,7 @@ export default class AdminProductHandler {
|
|||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
await this.bot.editMessageText(
|
await bot.editMessageText(
|
||||||
`📍 Select district in ${city}:`,
|
`📍 Select district in ${city}:`,
|
||||||
{
|
{
|
||||||
chat_id: chatId,
|
chat_id: chatId,
|
||||||
@@ -125,29 +124,27 @@ export default class AdminProductHandler {
|
|||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in handleCitySelection:', error);
|
console.error('Error in handleCitySelection:', error);
|
||||||
await this.bot.sendMessage(chatId, 'Error loading districts. Please try again.');
|
await bot.sendMessage(chatId, 'Error loading districts. Please try again.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleDistrictSelection(callbackQuery) {
|
static async handleDistrictSelection(callbackQuery) {
|
||||||
|
if (!this.isAdmin(callbackQuery.from.id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const chatId = callbackQuery.message.chat.id;
|
const chatId = callbackQuery.message.chat.id;
|
||||||
const messageId = callbackQuery.message.message_id;
|
const messageId = callbackQuery.message.message_id;
|
||||||
const [country, city, district] = callbackQuery.data.replace('prod_district_', '').split('_');
|
const [country, city, district] = callbackQuery.data.replace('prod_district_', '').split('_');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const location = await db.getAsync(
|
const location = await LocationService.getLocation(country, city, district);
|
||||||
'SELECT id FROM locations WHERE country = ? AND city = ? AND district = ?',
|
|
||||||
[country, city, district]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!location) {
|
if (!location) {
|
||||||
throw new Error('Location not found');
|
throw new Error('Location not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
const categories = await db.allAsync(
|
const categories = await CategoryService.getCategoriesByLocationId(location.id);
|
||||||
'SELECT id, name FROM categories WHERE location_id = ? ORDER BY name',
|
|
||||||
[location.id]
|
|
||||||
);
|
|
||||||
|
|
||||||
const keyboard = {
|
const keyboard = {
|
||||||
inline_keyboard: [
|
inline_keyboard: [
|
||||||
@@ -160,7 +157,7 @@ export default class AdminProductHandler {
|
|||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
await this.bot.editMessageText(
|
await bot.editMessageText(
|
||||||
'📦 Select or add category:',
|
'📦 Select or add category:',
|
||||||
{
|
{
|
||||||
chat_id: chatId,
|
chat_id: chatId,
|
||||||
@@ -170,15 +167,22 @@ export default class AdminProductHandler {
|
|||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in handleDistrictSelection:', error);
|
console.error('Error in handleDistrictSelection:', error);
|
||||||
await this.bot.sendMessage(chatId, 'Error loading categories. Please try again.');
|
await bot.sendMessage(chatId, 'Error loading categories. Please try again.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleCategoryInput(msg) {
|
static async handleCategoryInput(msg) {
|
||||||
const chatId = msg.chat.id;
|
const chatId = msg.chat.id;
|
||||||
const state = this.userStates.get(chatId);
|
const state = userStates.get(chatId);
|
||||||
|
|
||||||
if (!state || !state.action?.startsWith('add_category_')) return false;
|
if (!state || !state.action?.startsWith('add_category_')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.isAdmin(msg.from.id)) {
|
||||||
|
await bot.sendMessage(chatId, 'Unauthorized access.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const locationId = state.action.replace('add_category_', '');
|
const locationId = state.action.replace('add_category_', '');
|
||||||
@@ -188,12 +192,9 @@ export default class AdminProductHandler {
|
|||||||
[locationId, msg.text]
|
[locationId, msg.text]
|
||||||
);
|
);
|
||||||
|
|
||||||
const location = await db.getAsync(
|
const location = await LocationService.getLocationById(locationId);
|
||||||
'SELECT country, city, district FROM locations WHERE id = ?',
|
|
||||||
[locationId]
|
|
||||||
);
|
|
||||||
|
|
||||||
await this.bot.sendMessage(
|
await bot.sendMessage(
|
||||||
chatId,
|
chatId,
|
||||||
`✅ Category "${msg.text}" added successfully!`,
|
`✅ Category "${msg.text}" added successfully!`,
|
||||||
{
|
{
|
||||||
@@ -208,26 +209,30 @@ export default class AdminProductHandler {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
this.userStates.delete(chatId);
|
userStates.delete(chatId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.code === 'SQLITE_CONSTRAINT') {
|
if (error.code === 'SQLITE_CONSTRAINT') {
|
||||||
await this.bot.sendMessage(chatId, 'This category already exists in this location.');
|
await bot.sendMessage(chatId, 'This category already exists in this location.');
|
||||||
} else {
|
} else {
|
||||||
console.error('Error adding category:', error);
|
console.error('Error adding category:', error);
|
||||||
await this.bot.sendMessage(chatId, 'Error adding category. Please try again.');
|
await bot.sendMessage(chatId, 'Error adding category. Please try again.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleAddCategory(callbackQuery) {
|
static async handleAddCategory(callbackQuery) {
|
||||||
|
if (!this.isAdmin(callbackQuery.from.id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const chatId = callbackQuery.message.chat.id;
|
const chatId = callbackQuery.message.chat.id;
|
||||||
const locationId = callbackQuery.data.replace('add_category_', '');
|
const locationId = callbackQuery.data.replace('add_category_', '');
|
||||||
|
|
||||||
this.userStates.set(chatId, {action: `add_category_${locationId}`});
|
userStates.set(chatId, {action: `add_category_${locationId}`});
|
||||||
|
|
||||||
await this.bot.editMessageText(
|
await bot.editMessageText(
|
||||||
'Please enter the name for the new category:',
|
'Please enter the name for the new category:',
|
||||||
{
|
{
|
||||||
chat_id: chatId,
|
chat_id: chatId,
|
||||||
@@ -241,19 +246,19 @@ export default class AdminProductHandler {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleCategorySelection(callbackQuery) {
|
static async handleCategorySelection(callbackQuery) {
|
||||||
|
if (!this.isAdmin(callbackQuery.from.id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const chatId = callbackQuery.message.chat.id;
|
const chatId = callbackQuery.message.chat.id;
|
||||||
const messageId = callbackQuery.message.message_id;
|
const messageId = callbackQuery.message.message_id;
|
||||||
const [locationId, categoryId] = callbackQuery.data.replace('prod_category_', '').split('_');
|
const [locationId, categoryId] = callbackQuery.data.replace('prod_category_', '').split('_');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const subcategories = await db.allAsync(
|
const subcategories = await CategoryService.getSubcategoriesByCategoryId(categoryId);
|
||||||
'SELECT id, name FROM subcategories WHERE category_id = ? ORDER BY name',
|
const category = await CategoryService.getCategoryById(categoryId);
|
||||||
[categoryId]
|
const location = await LocationService.getLocationById(locationId);
|
||||||
);
|
|
||||||
|
|
||||||
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 = {
|
const keyboard = {
|
||||||
inline_keyboard: [
|
inline_keyboard: [
|
||||||
@@ -270,7 +275,7 @@ export default class AdminProductHandler {
|
|||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
await this.bot.editMessageText(
|
await bot.editMessageText(
|
||||||
`📦 Category: ${category.name}\nSelect or add subcategory:`,
|
`📦 Category: ${category.name}\nSelect or add subcategory:`,
|
||||||
{
|
{
|
||||||
chat_id: chatId,
|
chat_id: chatId,
|
||||||
@@ -280,15 +285,22 @@ export default class AdminProductHandler {
|
|||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in handleCategorySelection:', error);
|
console.error('Error in handleCategorySelection:', error);
|
||||||
await this.bot.sendMessage(chatId, 'Error loading subcategories. Please try again.');
|
await bot.sendMessage(chatId, 'Error loading subcategories. Please try again.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleSubcategoryInput(msg) {
|
static async handleSubcategoryInput(msg) {
|
||||||
const chatId = msg.chat.id;
|
const chatId = msg.chat.id;
|
||||||
const state = this.userStates.get(chatId);
|
const state = userStates.get(chatId);
|
||||||
|
|
||||||
if (!state || !state.action?.startsWith('add_subcategory_')) return false;
|
if (!state || !state.action?.startsWith('add_subcategory_')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.isAdmin(msg.from.id)) {
|
||||||
|
await bot.sendMessage(chatId, 'Unauthorized access.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const [locationId, categoryId] = state.action.replace('add_subcategory_', '').split('_');
|
const [locationId, categoryId] = state.action.replace('add_subcategory_', '').split('_');
|
||||||
@@ -298,7 +310,7 @@ export default class AdminProductHandler {
|
|||||||
[categoryId, msg.text]
|
[categoryId, msg.text]
|
||||||
);
|
);
|
||||||
|
|
||||||
await this.bot.sendMessage(
|
await bot.sendMessage(
|
||||||
chatId,
|
chatId,
|
||||||
`✅ Subcategory "${msg.text}" added successfully!`,
|
`✅ Subcategory "${msg.text}" added successfully!`,
|
||||||
{
|
{
|
||||||
@@ -313,26 +325,30 @@ export default class AdminProductHandler {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
this.userStates.delete(chatId);
|
userStates.delete(chatId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.code === 'SQLITE_CONSTRAINT') {
|
if (error.code === 'SQLITE_CONSTRAINT') {
|
||||||
await this.bot.sendMessage(chatId, 'This subcategory already exists in this category.');
|
await bot.sendMessage(chatId, 'This subcategory already exists in this category.');
|
||||||
} else {
|
} else {
|
||||||
console.error('Error adding subcategory:', error);
|
console.error('Error adding subcategory:', error);
|
||||||
await this.bot.sendMessage(chatId, 'Error adding subcategory. Please try again.');
|
await bot.sendMessage(chatId, 'Error adding subcategory. Please try again.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleAddSubcategory(callbackQuery) {
|
static async handleAddSubcategory(callbackQuery) {
|
||||||
|
if (!this.isAdmin(callbackQuery.from.id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const chatId = callbackQuery.message.chat.id;
|
const chatId = callbackQuery.message.chat.id;
|
||||||
const [locationId, categoryId] = callbackQuery.data.replace('add_subcategory_', '').split('_');
|
const [locationId, categoryId] = callbackQuery.data.replace('add_subcategory_', '').split('_');
|
||||||
|
|
||||||
this.userStates.set(chatId, {action: `add_subcategory_${locationId}_${categoryId}`});
|
userStates.set(chatId, {action: `add_subcategory_${locationId}_${categoryId}`});
|
||||||
|
|
||||||
await this.bot.editMessageText(
|
await bot.editMessageText(
|
||||||
'Please enter the name for the new subcategory:',
|
'Please enter the name for the new subcategory:',
|
||||||
{
|
{
|
||||||
chat_id: chatId,
|
chat_id: chatId,
|
||||||
@@ -346,7 +362,7 @@ export default class AdminProductHandler {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async viewProductsPage(locationId, categoryId, subcategoryId, page) {
|
static async viewProductsPage(locationId, categoryId, subcategoryId, page) {
|
||||||
try {
|
try {
|
||||||
const limit = 10;
|
const limit = 10;
|
||||||
const offset = (page || 0) * limit;
|
const offset = (page || 0) * limit;
|
||||||
@@ -424,27 +440,31 @@ export default class AdminProductHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleSubcategorySelection(callbackQuery) {
|
static async handleSubcategorySelection(callbackQuery) {
|
||||||
|
if (!this.isAdmin(callbackQuery.from.id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const chatId = callbackQuery.message.chat.id;
|
const chatId = callbackQuery.message.chat.id;
|
||||||
const messageId = callbackQuery.message.message_id;
|
const messageId = callbackQuery.message.message_id;
|
||||||
const [locationId, categoryId, subcategoryId] = callbackQuery.data.replace('prod_subcategory_', '').split('_');
|
const [locationId, categoryId, subcategoryId] = callbackQuery.data.replace('prod_subcategory_', '').split('_');
|
||||||
|
|
||||||
const state = this.userStates.get(chatId)
|
const state = userStates.get(chatId)
|
||||||
|
|
||||||
for (const msgId of state?.msgToDelete || []) {
|
for (const msgId of state?.msgToDelete || []) {
|
||||||
try {
|
try {
|
||||||
await this.bot.deleteMessage(chatId, msgId);
|
await bot.deleteMessage(chatId, msgId);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// ignore if can't delete
|
// ignore if can't delete
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.userStates.delete(chatId);
|
userStates.delete(chatId);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const {text, markup} = await this.viewProductsPage(locationId, categoryId, subcategoryId, 0);
|
const {text, markup} = await this.viewProductsPage(locationId, categoryId, subcategoryId, 0);
|
||||||
|
|
||||||
await this.bot.editMessageText(
|
await bot.editMessageText(
|
||||||
text,
|
text,
|
||||||
{
|
{
|
||||||
chat_id: chatId,
|
chat_id: chatId,
|
||||||
@@ -454,11 +474,11 @@ export default class AdminProductHandler {
|
|||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in handleSubcategorySelection:', error);
|
console.error('Error in handleSubcategorySelection:', error);
|
||||||
await this.bot.sendMessage(chatId, 'Error loading products. Please try again.');
|
await bot.sendMessage(chatId, 'Error loading products. Please try again.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleProductListPage(callbackQuery) {
|
static async handleProductListPage(callbackQuery) {
|
||||||
if (!this.isAdmin(callbackQuery.from.id)) {
|
if (!this.isAdmin(callbackQuery.from.id)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -469,7 +489,7 @@ export default class AdminProductHandler {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const {text, markup} = await this.viewProductsPage(locationId, categoryId, subcategoryId, parseInt(page));
|
const {text, markup} = await this.viewProductsPage(locationId, categoryId, subcategoryId, parseInt(page));
|
||||||
await this.bot.editMessageText(text, {
|
await bot.editMessageText(text, {
|
||||||
chat_id: chatId,
|
chat_id: chatId,
|
||||||
message_id: callbackQuery.message.message_id,
|
message_id: callbackQuery.message.message_id,
|
||||||
reply_markup: markup,
|
reply_markup: markup,
|
||||||
@@ -480,19 +500,16 @@ export default class AdminProductHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleAddProduct(callbackQuery) {
|
static async handleAddProduct(callbackQuery) {
|
||||||
|
if (!this.isAdmin(callbackQuery.from.id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const chatId = callbackQuery.message.chat.id;
|
const chatId = callbackQuery.message.chat.id;
|
||||||
const messageId = callbackQuery.message.message_id;
|
const messageId = callbackQuery.message.message_id;
|
||||||
const [locationId, categoryId, subcategoryId] = callbackQuery.data.replace('add_product_', '').split('_');
|
const [locationId, categoryId, subcategoryId] = callbackQuery.data.replace('add_product_', '').split('_');
|
||||||
|
|
||||||
try {
|
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 = [
|
const sampleProducts = [
|
||||||
{
|
{
|
||||||
name: "Sample Product 1",
|
name: "Sample Product 1",
|
||||||
@@ -510,14 +527,14 @@ export default class AdminProductHandler {
|
|||||||
const jsonExample = JSON.stringify(sampleProducts, null, 2);
|
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`;
|
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, {
|
userStates.set(chatId, {
|
||||||
action: 'import_products',
|
action: 'import_products',
|
||||||
locationId,
|
locationId,
|
||||||
categoryId,
|
categoryId,
|
||||||
subcategoryId
|
subcategoryId
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.bot.editMessageText(message, {
|
await bot.editMessageText(message, {
|
||||||
chat_id: chatId,
|
chat_id: chatId,
|
||||||
message_id: messageId,
|
message_id: messageId,
|
||||||
parse_mode: 'HTML',
|
parse_mode: 'HTML',
|
||||||
@@ -532,15 +549,22 @@ export default class AdminProductHandler {
|
|||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in handleAddProduct:', error);
|
console.error('Error in handleAddProduct:', error);
|
||||||
await this.bot.sendMessage(chatId, 'Error preparing product import. Please try again.');
|
await bot.sendMessage(chatId, 'Error preparing product import. Please try again.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleProductImport(msg) {
|
static async handleProductImport(msg) {
|
||||||
const chatId = msg.chat.id;
|
const chatId = msg.chat.id;
|
||||||
const state = this.userStates.get(chatId);
|
const state = userStates.get(chatId);
|
||||||
|
|
||||||
if (!state || state.action !== 'import_products') return false;
|
if (!state || state.action !== 'import_products') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.isAdmin(msg.from.id)) {
|
||||||
|
await bot.sendMessage(chatId, 'Unauthorized access.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let products;
|
let products;
|
||||||
@@ -549,20 +573,20 @@ export default class AdminProductHandler {
|
|||||||
// Handle file upload
|
// Handle file upload
|
||||||
if (msg.document) {
|
if (msg.document) {
|
||||||
if (!msg.document.file_name.endsWith('.json')) {
|
if (!msg.document.file_name.endsWith('.json')) {
|
||||||
await this.bot.sendMessage(chatId, 'Please upload a .json file.');
|
await bot.sendMessage(chatId, 'Please upload a .json file.');
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const file = await this.bot.getFile(msg.document.file_id);
|
const file = await bot.getFile(msg.document.file_id);
|
||||||
|
|
||||||
const fileContent = await this.bot.downloadFile(file.file_id, '.');
|
const fileContent = await bot.downloadFile(file.file_id, '.');
|
||||||
jsonContent = await fs.readFile(fileContent, 'utf8');
|
jsonContent = await fs.readFile(fileContent, 'utf8');
|
||||||
await fs.rm(fileContent);
|
await fs.rm(fileContent);
|
||||||
|
|
||||||
} else if (msg.text) {
|
} else if (msg.text) {
|
||||||
jsonContent = msg.text;
|
jsonContent = msg.text;
|
||||||
} else {
|
} else {
|
||||||
await this.bot.sendMessage(chatId, 'Please send either a JSON file or JSON text.');
|
await bot.sendMessage(chatId, 'Please send either a JSON file or JSON text.');
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -572,7 +596,7 @@ export default class AdminProductHandler {
|
|||||||
throw new Error('Input must be an array of products');
|
throw new Error('Input must be an array of products');
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
await this.bot.sendMessage(chatId, 'Invalid JSON format. Please check the format and try again.');
|
await bot.sendMessage(chatId, 'Invalid JSON format. Please check the format and try again.');
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -597,7 +621,7 @@ export default class AdminProductHandler {
|
|||||||
|
|
||||||
await db.runAsync('COMMIT');
|
await db.runAsync('COMMIT');
|
||||||
|
|
||||||
await this.bot.sendMessage(
|
await bot.sendMessage(
|
||||||
chatId,
|
chatId,
|
||||||
`✅ Successfully imported ${products.length} products!`,
|
`✅ Successfully imported ${products.length} products!`,
|
||||||
{
|
{
|
||||||
@@ -612,21 +636,28 @@ export default class AdminProductHandler {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
this.userStates.delete(chatId);
|
userStates.delete(chatId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
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 bot.sendMessage(chatId, 'Error importing products. Please check the data and try again.');
|
||||||
await db.runAsync('ROLLBACK');
|
await db.runAsync('ROLLBACK');
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleProductEditImport(msg) {
|
static async handleProductEditImport(msg) {
|
||||||
const chatId = msg.chat.id;
|
const chatId = msg.chat.id;
|
||||||
const state = this.userStates.get(chatId);
|
const state = userStates.get(chatId);
|
||||||
|
|
||||||
if (!state || state.action !== 'edit_product') return false;
|
if (!state || state.action !== 'edit_product') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.isAdmin(msg.from.id)) {
|
||||||
|
await bot.sendMessage(chatId, 'Unauthorized access.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let product;
|
let product;
|
||||||
@@ -635,34 +666,34 @@ export default class AdminProductHandler {
|
|||||||
// Handle file upload
|
// Handle file upload
|
||||||
if (msg.document) {
|
if (msg.document) {
|
||||||
if (!msg.document.file_name.endsWith('.json')) {
|
if (!msg.document.file_name.endsWith('.json')) {
|
||||||
await this.bot.sendMessage(chatId, 'Please upload a .json file.');
|
await bot.sendMessage(chatId, 'Please upload a .json file.');
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const file = await this.bot.getFile(msg.document.file_id);
|
const file = await bot.getFile(msg.document.file_id);
|
||||||
|
|
||||||
const fileContent = await this.bot.downloadFile(file.file_id, '.');
|
const fileContent = await bot.downloadFile(file.file_id, '.');
|
||||||
jsonContent = await fs.readFile(fileContent, 'utf8');
|
jsonContent = await fs.readFile(fileContent, 'utf8');
|
||||||
await fs.rm(fileContent);
|
await fs.rm(fileContent);
|
||||||
|
|
||||||
} else if (msg.text) {
|
} else if (msg.text) {
|
||||||
jsonContent = msg.text;
|
jsonContent = msg.text;
|
||||||
} else {
|
} else {
|
||||||
await this.bot.sendMessage(chatId, 'Please send either a JSON file or JSON text.');
|
await bot.sendMessage(chatId, 'Please send either a JSON file or JSON text.');
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
product = JSON.parse(jsonContent);
|
product = JSON.parse(jsonContent);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
await this.bot.sendMessage(chatId, 'Invalid JSON format. Please check the format and try again.');
|
await bot.sendMessage(chatId, 'Invalid JSON format. Please check the format and try again.');
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
await db.runAsync('BEGIN TRANSACTION');
|
await db.runAsync('BEGIN TRANSACTION');
|
||||||
|
|
||||||
await db.runAsync(
|
await db.runAsync(
|
||||||
`UPDATE products SET
|
`UPDATE products SET
|
||||||
location_id = ?,
|
location_id = ?,
|
||||||
category_id = ?,
|
category_id = ?,
|
||||||
subcategory_id = ?,
|
subcategory_id = ?,
|
||||||
@@ -678,17 +709,17 @@ export default class AdminProductHandler {
|
|||||||
WHERE
|
WHERE
|
||||||
id = ?
|
id = ?
|
||||||
`,
|
`,
|
||||||
[
|
[
|
||||||
state.locationId, state.categoryId, state.subcategoryId,
|
state.locationId, state.categoryId, state.subcategoryId,
|
||||||
product.name, product.price, product.description, product.private_data,
|
product.name, product.price, product.description, product.private_data,
|
||||||
product.quantity_in_stock, product.photo_url, product.hidden_photo_url,
|
product.quantity_in_stock, product.photo_url, product.hidden_photo_url,
|
||||||
product.hidden_coordinates, product.hidden_description, state.productId
|
product.hidden_coordinates, product.hidden_description, state.productId
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
await db.runAsync('COMMIT');
|
await db.runAsync('COMMIT');
|
||||||
|
|
||||||
await this.bot.sendMessage(
|
await bot.sendMessage(
|
||||||
chatId,
|
chatId,
|
||||||
`✅ Successfully edited!`,
|
`✅ Successfully edited!`,
|
||||||
{
|
{
|
||||||
@@ -703,32 +734,27 @@ export default class AdminProductHandler {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
this.userStates.delete(chatId);
|
userStates.delete(chatId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
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 bot.sendMessage(chatId, 'Error importing products. Please check the data and try again.');
|
||||||
await db.runAsync('ROLLBACK');
|
await db.runAsync('ROLLBACK');
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleViewProduct(callbackQuery) {
|
static async handleViewProduct(callbackQuery) {
|
||||||
|
if (!this.isAdmin(callbackQuery.from.id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const chatId = callbackQuery.message.chat.id;
|
const chatId = callbackQuery.message.chat.id;
|
||||||
const messageId = callbackQuery.message.message_id;
|
const messageId = callbackQuery.message.message_id;
|
||||||
const productId = callbackQuery.data.replace('view_product_', '');
|
const productId = callbackQuery.data.replace('view_product_', '');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const product = await db.getAsync(
|
const product = await ProductService.getDetailedProductById(productId)
|
||||||
`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) {
|
if (!product) {
|
||||||
throw new Error('Product not found');
|
throw new Error('Product not found');
|
||||||
@@ -770,47 +796,42 @@ Coordinates: ${product.hidden_coordinates}
|
|||||||
// Send product photos
|
// Send product photos
|
||||||
if (product.photo_url) {
|
if (product.photo_url) {
|
||||||
try {
|
try {
|
||||||
photoMessage = await this.bot.sendPhoto(chatId, product.photo_url, {caption: 'Public photo'});
|
photoMessage = await bot.sendPhoto(chatId, product.photo_url, {caption: 'Public photo'});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
photoMessage = await this.bot.sendPhoto(chatId, "./corrupt-photo.jpg", {caption: 'Public photo'})
|
photoMessage = await bot.sendPhoto(chatId, "./corrupt-photo.jpg", {caption: 'Public photo'})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (product.hidden_photo_url) {
|
if (product.hidden_photo_url) {
|
||||||
try {
|
try {
|
||||||
hiddenPhotoMessage = await this.bot.sendPhoto(chatId, product.hidden_photo_url, {caption: 'Hidden photo'});
|
hiddenPhotoMessage = await bot.sendPhoto(chatId, product.hidden_photo_url, {caption: 'Hidden photo'});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
hiddenPhotoMessage = await this.bot.sendPhoto(chatId, "./corrupt-photo.jpg", {caption: 'Hidden photo'})
|
hiddenPhotoMessage = await bot.sendPhoto(chatId, "./corrupt-photo.jpg", {caption: 'Hidden photo'})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.userStates.set(chatId, {
|
userStates.set(chatId, {
|
||||||
msgToDelete: [photoMessage.message_id, hiddenPhotoMessage.message_id]
|
msgToDelete: [photoMessage.message_id, hiddenPhotoMessage.message_id]
|
||||||
})
|
})
|
||||||
|
|
||||||
await this.bot.deleteMessage(chatId, messageId);
|
await bot.deleteMessage(chatId, messageId);
|
||||||
await this.bot.sendMessage(chatId, message, {reply_markup: keyboard});
|
await bot.sendMessage(chatId, message, {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 bot.sendMessage(chatId, 'Error loading product details. Please try again.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleProductEdit(callbackQuery) {
|
static async handleProductEdit(callbackQuery) {
|
||||||
|
if (!this.isAdmin(callbackQuery.from.id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const chatId = callbackQuery.message.chat.id;
|
const chatId = callbackQuery.message.chat.id;
|
||||||
const messageId = callbackQuery.message.message_id;
|
const messageId = callbackQuery.message.message_id;
|
||||||
const productId = callbackQuery.data.replace('edit_product_', '');
|
const productId = callbackQuery.data.replace('edit_product_', '');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const product = await db.getAsync(
|
const product = await ProductService.getDetailedProductById(productId);
|
||||||
`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) {
|
if (!product) {
|
||||||
throw new Error('Product not found');
|
throw new Error('Product not found');
|
||||||
@@ -835,7 +856,7 @@ Coordinates: ${product.hidden_coordinates}
|
|||||||
const jsonExample = JSON.stringify(sampleProduct, null, 2);
|
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`;
|
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, {
|
userStates.set(chatId, {
|
||||||
action: 'edit_product',
|
action: 'edit_product',
|
||||||
locationId,
|
locationId,
|
||||||
categoryId,
|
categoryId,
|
||||||
@@ -843,7 +864,7 @@ Coordinates: ${product.hidden_coordinates}
|
|||||||
productId
|
productId
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.bot.editMessageText(message, {
|
await bot.editMessageText(message, {
|
||||||
chat_id: chatId,
|
chat_id: chatId,
|
||||||
message_id: messageId,
|
message_id: messageId,
|
||||||
parse_mode: 'HTML',
|
parse_mode: 'HTML',
|
||||||
@@ -858,27 +879,20 @@ Coordinates: ${product.hidden_coordinates}
|
|||||||
});
|
});
|
||||||
} 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 bot.sendMessage(chatId, 'Error loading product details. Please try again.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleProductDelete(callbackQuery) {
|
static async handleProductDelete(callbackQuery) {
|
||||||
if (!this.isAdmin(callbackQuery.from.id)) return;
|
if (!this.isAdmin(callbackQuery.from.id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const productId = callbackQuery.data.replace('delete_product_', '');
|
const productId = callbackQuery.data.replace('delete_product_', '');
|
||||||
const chatId = callbackQuery.message.chat.id;
|
const chatId = callbackQuery.message.chat.id;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const product = await db.getAsync(
|
const product = await ProductService.getDetailedProductById(productId);
|
||||||
`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) {
|
if (!product) {
|
||||||
throw new Error('Product not found');
|
throw new Error('Product not found');
|
||||||
@@ -896,7 +910,7 @@ Coordinates: ${product.hidden_coordinates}
|
|||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
await this.bot.editMessageText(
|
await bot.editMessageText(
|
||||||
`⚠️ Are you sure you want to delete product\n\nThis action cannot be undone!`,
|
`⚠️ Are you sure you want to delete product\n\nThis action cannot be undone!`,
|
||||||
{
|
{
|
||||||
chat_id: chatId,
|
chat_id: chatId,
|
||||||
@@ -907,41 +921,33 @@ Coordinates: ${product.hidden_coordinates}
|
|||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in handleDeleteUser:', error);
|
console.error('Error in handleDeleteUser:', error);
|
||||||
await this.bot.sendMessage(chatId, 'Error processing delete request. Please try again.');
|
await bot.sendMessage(chatId, 'Error processing delete request. Please try again.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleConfirmDelete(callbackQuery) {
|
static async handleConfirmDelete(callbackQuery) {
|
||||||
if (!this.isAdmin(callbackQuery.from.id)) return;
|
if (!this.isAdmin(callbackQuery.from.id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const productId = callbackQuery.data.replace('confirm_delete_product_', '');
|
const productId = callbackQuery.data.replace('confirm_delete_product_', '');
|
||||||
const chatId = callbackQuery.message.chat.id;
|
const chatId = callbackQuery.message.chat.id;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const product = await db.getAsync(
|
const product = await ProductService.getDetailedProductById(productId);
|
||||||
`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) {
|
if (!product) {
|
||||||
throw new Error('Product not found');
|
throw new Error('Product not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await db.runAsync('BEGIN TRANSACTION');
|
await db.runAsync('BEGIN TRANSACTION');
|
||||||
await db.runAsync('DELETE FROM products WHERE id=?', [productId.toString()]);
|
await db.runAsync('DELETE FROM products WHERE id=?', [productId.toString()]);
|
||||||
await db.runAsync('COMMIT');
|
await db.runAsync('COMMIT');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
await db.runAsync("ROLLBACK");
|
await db.runAsync("ROLLBACK");
|
||||||
console.error('Error deleting product:', error);
|
console.error('Error deleting product:', e);
|
||||||
throw error;
|
throw e;
|
||||||
}
|
}
|
||||||
|
|
||||||
const keyboard = {
|
const keyboard = {
|
||||||
@@ -953,7 +959,7 @@ Coordinates: ${product.hidden_coordinates}
|
|||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
await this.bot.editMessageText(
|
await bot.editMessageText(
|
||||||
`✅ Product has been successfully deleted.`,
|
`✅ Product has been successfully deleted.`,
|
||||||
{
|
{
|
||||||
chat_id: chatId,
|
chat_id: chatId,
|
||||||
@@ -963,7 +969,7 @@ Coordinates: ${product.hidden_coordinates}
|
|||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in handleConfirmDelete:', error);
|
console.error('Error in handleConfirmDelete:', error);
|
||||||
await this.bot.sendMessage(chatId, 'Error deleting product. Please try again.');
|
await bot.sendMessage(chatId, 'Error deleting product. Please try again.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,18 +1,15 @@
|
|||||||
import User from '../models/User.js';
|
import config from '../../config/config.js';
|
||||||
import config from '../config/config.js';
|
import db from '../../config/database.js';
|
||||||
import db from '../config/database.js';
|
import bot from "../../context/bot.js";
|
||||||
|
import UserService from "../../services/userService.js";
|
||||||
|
import userStates from "../../context/userStates.js";
|
||||||
|
|
||||||
export default class AdminUserHandler {
|
export default class AdminUserHandler {
|
||||||
constructor(bot) {
|
static isAdmin(userId) {
|
||||||
this.bot = bot;
|
|
||||||
this.userStates = new Map();
|
|
||||||
}
|
|
||||||
|
|
||||||
isAdmin(userId) {
|
|
||||||
return config.ADMIN_IDS.includes(userId.toString());
|
return config.ADMIN_IDS.includes(userId.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
async calculateStatistics() {
|
static async calculateStatistics() {
|
||||||
try {
|
try {
|
||||||
const users = await db.allAsync(`
|
const users = await db.allAsync(`
|
||||||
SELECT
|
SELECT
|
||||||
@@ -48,7 +45,7 @@ export default class AdminUserHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async viewUserPage(page) {
|
static async viewUserPage(page) {
|
||||||
const limit = 10;
|
const limit = 10;
|
||||||
const offset = (page || 0) * limit;
|
const offset = (page || 0) * limit;
|
||||||
|
|
||||||
@@ -101,17 +98,17 @@ export default class AdminUserHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleUserList(msg) {
|
static async handleUserList(msg) {
|
||||||
if (!this.isAdmin(msg.from.id)) {
|
if (!this.isAdmin(msg.from.id)) {
|
||||||
await this.bot.sendMessage(msg.chat.id, 'Unauthorized access.');
|
await bot.sendMessage(msg.chat.id, 'Unauthorized access.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const {text, markup} = await this.viewUserPage(0);
|
const {text, markup} = await this.viewUserPage(0);
|
||||||
await this.bot.sendMessage(msg.chat.id, text, {reply_markup: markup})
|
await bot.sendMessage(msg.chat.id, text, {reply_markup: markup})
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleUserListPage(callbackQuery) {
|
static async handleUserListPage(callbackQuery) {
|
||||||
if (!this.isAdmin(callbackQuery.from.id)) {
|
if (!this.isAdmin(callbackQuery.from.id)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -121,7 +118,7 @@ export default class AdminUserHandler {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const {text, markup} = await this.viewUserPage(page);
|
const {text, markup} = await this.viewUserPage(page);
|
||||||
await this.bot.editMessageText(text, {
|
await bot.editMessageText(text, {
|
||||||
chat_id: chatId,
|
chat_id: chatId,
|
||||||
message_id: callbackQuery.message.message_id,
|
message_id: callbackQuery.message.message_id,
|
||||||
reply_markup: markup,
|
reply_markup: markup,
|
||||||
@@ -132,37 +129,18 @@ export default class AdminUserHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleViewUser(callbackQuery) {
|
static async handleViewUser(callbackQuery) {
|
||||||
if (!this.isAdmin(callbackQuery.from.id)) return;
|
if (!this.isAdmin(callbackQuery.from.id)) return;
|
||||||
|
|
||||||
const userId = callbackQuery.data.replace('view_user_', '');
|
const telegramId = callbackQuery.data.replace('view_user_', '');
|
||||||
const chatId = callbackQuery.message.chat.id;
|
const chatId = callbackQuery.message.chat.id;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const userStats = await db.getAsync(`
|
const detailedUser = await UserService.getDetailedUserByTelegramId(telegramId);
|
||||||
SELECT
|
const user = await UserService.getUserByTelegramId(telegramId);
|
||||||
u.*,
|
|
||||||
COUNT(DISTINCT p.id) as purchase_count,
|
|
||||||
COALESCE(SUM(p.total_price), 0) as total_spent,
|
|
||||||
COUNT(DISTINCT cw.id) as wallet_count,
|
|
||||||
COALESCE(SUM(t.amount), 0) as total_balance
|
|
||||||
FROM users u
|
|
||||||
LEFT JOIN purchases p ON u.id = p.user_id
|
|
||||||
LEFT JOIN crypto_wallets cw ON u.id = cw.user_id
|
|
||||||
LEFT JOIN transactions t ON u.id = t.user_id
|
|
||||||
WHERE u.telegram_id = ?
|
|
||||||
GROUP BY u.id
|
|
||||||
`, [userId]);
|
|
||||||
|
|
||||||
const user = await User.getById(userId);
|
if (!detailedUser) {
|
||||||
|
await bot.sendMessage(chatId, 'User not found.');
|
||||||
if (!user) {
|
|
||||||
await this.bot.sendMessage(chatId, 'User not found.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!userStats) {
|
|
||||||
await this.bot.sendMessage(chatId, 'User not found.');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,7 +152,7 @@ export default class AdminUserHandler {
|
|||||||
WHERE u.telegram_id = ?
|
WHERE u.telegram_id = ?
|
||||||
ORDER BY t.created_at DESC
|
ORDER BY t.created_at DESC
|
||||||
LIMIT 5
|
LIMIT 5
|
||||||
`, [userId]);
|
`, [telegramId]);
|
||||||
|
|
||||||
// Get recent purchases
|
// Get recent purchases
|
||||||
const purchases = await db.allAsync(`
|
const purchases = await db.allAsync(`
|
||||||
@@ -186,18 +164,18 @@ export default class AdminUserHandler {
|
|||||||
WHERE u.telegram_id = ?
|
WHERE u.telegram_id = ?
|
||||||
ORDER BY p.purchase_date DESC
|
ORDER BY p.purchase_date DESC
|
||||||
LIMIT 5
|
LIMIT 5
|
||||||
`, [userId]);
|
`, [telegramId]);
|
||||||
|
|
||||||
const message = `
|
const message = `
|
||||||
👤 User Profile:
|
👤 User Profile:
|
||||||
|
|
||||||
ID: ${userId}
|
ID: ${telegramId}
|
||||||
📍 Location: ${userStats.country || 'Not set'}, ${userStats.city || 'Not set'}, ${userStats.district || 'Not set'}
|
📍 Location: ${detailedUser.country || 'Not set'}, ${detailedUser.city || 'Not set'}, ${detailedUser.district || 'Not set'}
|
||||||
|
|
||||||
📊 Activity:
|
📊 Activity:
|
||||||
- Total Purchases: ${userStats.purchase_count}
|
- Total Purchases: ${detailedUser.purchase_count}
|
||||||
- Total Spent: $${userStats.total_spent || 0}
|
- Total Spent: $${detailedUser.total_spent || 0}
|
||||||
- Active Wallets: ${userStats.wallet_count}
|
- Active Wallets: ${detailedUser.wallet_count}
|
||||||
- Bonus Balance: $${user.bonus_balance || 0}
|
- Bonus Balance: $${user.bonus_balance || 0}
|
||||||
- Total Balance: $${(user.total_balance || 0) + (user.bonus_balance || 0)}
|
- Total Balance: $${(user.total_balance || 0) + (user.bonus_balance || 0)}
|
||||||
|
|
||||||
@@ -207,24 +185,24 @@ ${transactions.map(t => ` • ${t.amount} ${t.wallet_type} (${t.tx_hash})`).joi
|
|||||||
🛍 Recent Purchases:
|
🛍 Recent Purchases:
|
||||||
${purchases.map(p => ` • ${p.product_name} x${p.quantity} - $${p.total_price}`).join('\n')}
|
${purchases.map(p => ` • ${p.product_name} x${p.quantity} - $${p.total_price}`).join('\n')}
|
||||||
|
|
||||||
📅 Registered: ${new Date(userStats.created_at).toLocaleString()}
|
📅 Registered: ${new Date(detailedUser.created_at).toLocaleString()}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const keyboard = {
|
const keyboard = {
|
||||||
inline_keyboard: [
|
inline_keyboard: [
|
||||||
[
|
[
|
||||||
{text: '💰 Edit Balance', callback_data: `edit_user_balance_${userId}`},
|
{text: '💰 Edit Balance', callback_data: `edit_user_balance_${telegramId}`},
|
||||||
{text: '📍 Edit Location', callback_data: `edit_user_location_${userId}`}
|
{text: '📍 Edit Location', callback_data: `edit_user_location_${telegramId}`}
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
{text: '🚫 Block User', callback_data: `block_user_${userId}`},
|
{text: '🚫 Block User', callback_data: `block_user_${telegramId}`},
|
||||||
{text: '❌ Delete User', callback_data: `delete_user_${userId}`}
|
{text: '❌ Delete User', callback_data: `delete_user_${telegramId}`}
|
||||||
],
|
],
|
||||||
[{text: '« Back to User List', callback_data: `list_users_0`}]
|
[{text: '« Back to User List', callback_data: `list_users_0`}]
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
await this.bot.editMessageText(message, {
|
await bot.editMessageText(message, {
|
||||||
chat_id: chatId,
|
chat_id: chatId,
|
||||||
message_id: callbackQuery.message.message_id,
|
message_id: callbackQuery.message.message_id,
|
||||||
reply_markup: keyboard,
|
reply_markup: keyboard,
|
||||||
@@ -232,28 +210,30 @@ ${purchases.map(p => ` • ${p.product_name} x${p.quantity} - $${p.total_price}
|
|||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in handleViewUser:', error);
|
console.error('Error in handleViewUser:', error);
|
||||||
await this.bot.sendMessage(chatId, 'Error loading user details. Please try again.');
|
await bot.sendMessage(chatId, 'Error loading user details. Please try again.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleDeleteUser(callbackQuery) {
|
static async handleDeleteUser(callbackQuery) {
|
||||||
if (!this.isAdmin(callbackQuery.from.id)) return;
|
if (!this.isAdmin(callbackQuery.from.id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const userId = callbackQuery.data.replace('delete_user_', '');
|
const telegramId = callbackQuery.data.replace('delete_user_', '');
|
||||||
const chatId = callbackQuery.message.chat.id;
|
const chatId = callbackQuery.message.chat.id;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const keyboard = {
|
const keyboard = {
|
||||||
inline_keyboard: [
|
inline_keyboard: [
|
||||||
[
|
[
|
||||||
{text: '✅ Confirm Delete', callback_data: `confirm_delete_user_${userId}`},
|
{text: '✅ Confirm Delete', callback_data: `confirm_delete_user_${telegramId}`},
|
||||||
{text: '❌ Cancel', callback_data: `view_user_${userId}`}
|
{text: '❌ Cancel', callback_data: `view_user_${telegramId}`}
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
await this.bot.editMessageText(
|
await bot.editMessageText(
|
||||||
`⚠️ Are you sure you want to delete user ${userId}?\n\nThis action will:\n- Delete all user data\n- Remove all wallets\n- Erase purchase history\n\nThis action cannot be undone!`,
|
`⚠️ Are you sure you want to delete user ${telegramId}?\n\nThis action will:\n- Delete all user data\n- Remove all wallets\n- Erase purchase history\n\nThis action cannot be undone!`,
|
||||||
{
|
{
|
||||||
chat_id: chatId,
|
chat_id: chatId,
|
||||||
message_id: callbackQuery.message.message_id,
|
message_id: callbackQuery.message.message_id,
|
||||||
@@ -263,18 +243,20 @@ ${purchases.map(p => ` • ${p.product_name} x${p.quantity} - $${p.total_price}
|
|||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in handleDeleteUser:', error);
|
console.error('Error in handleDeleteUser:', error);
|
||||||
await this.bot.sendMessage(chatId, 'Error processing delete request. Please try again.');
|
await bot.sendMessage(chatId, 'Error processing delete request. Please try again.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleConfirmDelete(callbackQuery) {
|
static async handleConfirmDelete(callbackQuery) {
|
||||||
if (!this.isAdmin(callbackQuery.from.id)) return;
|
if (!this.isAdmin(callbackQuery.from.id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const userId = callbackQuery.data.replace('confirm_delete_user_', '');
|
const telegramId = callbackQuery.data.replace('confirm_delete_user_', '');
|
||||||
const chatId = callbackQuery.message.chat.id;
|
const chatId = callbackQuery.message.chat.id;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await User.updateUserStatus(userId, 1);
|
await UserService.updateUserStatus(telegramId, 1);
|
||||||
|
|
||||||
const keyboard = {
|
const keyboard = {
|
||||||
inline_keyboard: [
|
inline_keyboard: [
|
||||||
@@ -283,13 +265,13 @@ ${purchases.map(p => ` • ${p.product_name} x${p.quantity} - $${p.total_price}
|
|||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.bot.sendMessage(userId, '⚠️Your account has been deleted by administrator');
|
await bot.sendMessage(telegramId, '⚠️Your account has been deleted by administrator');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// ignore if we can't notify user
|
// ignore if we can't notify user
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.bot.editMessageText(
|
await bot.editMessageText(
|
||||||
`✅ User ${userId} has been successfully deleted.`,
|
`✅ User ${telegramId} has been successfully deleted.`,
|
||||||
{
|
{
|
||||||
chat_id: chatId,
|
chat_id: chatId,
|
||||||
message_id: callbackQuery.message.message_id,
|
message_id: callbackQuery.message.message_id,
|
||||||
@@ -298,28 +280,30 @@ ${purchases.map(p => ` • ${p.product_name} x${p.quantity} - $${p.total_price}
|
|||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in handleConfirmDelete:', error);
|
console.error('Error in handleConfirmDelete:', error);
|
||||||
await this.bot.sendMessage(chatId, 'Error deleting user. Please try again.');
|
await bot.sendMessage(chatId, 'Error deleting user. Please try again.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleBlockUser(callbackQuery) {
|
static async handleBlockUser(callbackQuery) {
|
||||||
if (!this.isAdmin(callbackQuery.from.id)) return;
|
if (!this.isAdmin(callbackQuery.from.id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const userId = callbackQuery.data.replace('block_user_', '');
|
const telegramId = callbackQuery.data.replace('block_user_', '');
|
||||||
const chatId = callbackQuery.message.chat.id;
|
const chatId = callbackQuery.message.chat.id;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const keyboard = {
|
const keyboard = {
|
||||||
inline_keyboard: [
|
inline_keyboard: [
|
||||||
[
|
[
|
||||||
{text: '✅ Confirm Block', callback_data: `confirm_block_user_${userId}`},
|
{text: '✅ Confirm Block', callback_data: `confirm_block_user_${telegramId}`},
|
||||||
{text: '❌ Cancel', callback_data: `view_user_${userId}`}
|
{text: '❌ Cancel', callback_data: `view_user_${telegramId}`}
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
await this.bot.editMessageText(
|
await bot.editMessageText(
|
||||||
`⚠️ Are you sure you want to block user ${userId}?`,
|
`⚠️ Are you sure you want to block user ${telegramId}?`,
|
||||||
{
|
{
|
||||||
chat_id: chatId,
|
chat_id: chatId,
|
||||||
message_id: callbackQuery.message.message_id,
|
message_id: callbackQuery.message.message_id,
|
||||||
@@ -329,18 +313,20 @@ ${purchases.map(p => ` • ${p.product_name} x${p.quantity} - $${p.total_price}
|
|||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in handleBlockUser:', error);
|
console.error('Error in handleBlockUser:', error);
|
||||||
await this.bot.sendMessage(chatId, 'Error processing block request. Please try again.');
|
await bot.sendMessage(chatId, 'Error processing block request. Please try again.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleConfirmBlock(callbackQuery) {
|
static async handleConfirmBlock(callbackQuery) {
|
||||||
if (!this.isAdmin(callbackQuery.from.id)) return;
|
if (!this.isAdmin(callbackQuery.from.id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const userId = callbackQuery.data.replace('confirm_block_user_', '');
|
const telegramId = callbackQuery.data.replace('confirm_block_user_', '');
|
||||||
const chatId = callbackQuery.message.chat.id;
|
const chatId = callbackQuery.message.chat.id;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await User.updateUserStatus(userId, 2);
|
await UserService.updateUserStatus(telegramId, 2);
|
||||||
|
|
||||||
const keyboard = {
|
const keyboard = {
|
||||||
inline_keyboard: [
|
inline_keyboard: [
|
||||||
@@ -349,13 +335,13 @@ ${purchases.map(p => ` • ${p.product_name} x${p.quantity} - $${p.total_price}
|
|||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.bot.sendMessage(userId, '⚠️Your account has been blocked by administrator');
|
await bot.sendMessage(telegramId, '⚠️Your account has been blocked by administrator');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// ignore if we can't notify user
|
// ignore if we can't notify user
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.bot.editMessageText(
|
await bot.editMessageText(
|
||||||
`✅ User ${userId} has been successfully blocked.`,
|
`✅ User ${telegramId} has been successfully blocked.`,
|
||||||
{
|
{
|
||||||
chat_id: chatId,
|
chat_id: chatId,
|
||||||
message_id: callbackQuery.message.message_id,
|
message_id: callbackQuery.message.message_id,
|
||||||
@@ -364,44 +350,48 @@ ${purchases.map(p => ` • ${p.product_name} x${p.quantity} - $${p.total_price}
|
|||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in handleConfirmBlock:', error);
|
console.error('Error in handleConfirmBlock:', error);
|
||||||
await this.bot.sendMessage(chatId, 'Error blocking user. Please try again.');
|
await bot.sendMessage(chatId, 'Error blocking user. Please try again.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleEditUserBalance(callbackQuery) {
|
static async handleEditUserBalance(callbackQuery) {
|
||||||
if (!this.isAdmin(callbackQuery.from.id)) return;
|
if (!this.isAdmin(callbackQuery.from.id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const userId = callbackQuery.data.replace('edit_user_balance_', '');
|
const telegramId = callbackQuery.data.replace('edit_user_balance_', '');
|
||||||
const chatId = callbackQuery.message.chat.id;
|
const chatId = callbackQuery.message.chat.id;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const user = await User.getById(userId);
|
const user = await UserService.getUserByTelegramId(telegramId);
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
await this.bot.sendMessage(chatId, 'User not found.');
|
await bot.sendMessage(chatId, 'User not found.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.bot.editMessageText(
|
await bot.editMessageText(
|
||||||
`Enter new value for bonus balance. \n\n👥 User: ${userId}\n💰 Bonus Balance Now: $${user.bonus_balance.toFixed(2)}`,
|
`Enter new value for bonus balance. \n\n👥 User: ${telegramId}\n💰 Bonus Balance Now: $${user.bonus_balance.toFixed(2)}`,
|
||||||
{
|
{
|
||||||
chat_id: chatId,
|
chat_id: chatId,
|
||||||
message_id: callbackQuery.message.message_id,
|
message_id: callbackQuery.message.message_id,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
this.userStates.set(chatId, { action: "edit_bonus_balance", telegram_id: userId });
|
userStates.set(chatId, { action: "edit_bonus_balance", telegram_id: telegramId });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in handleEditUserBalance:', error);
|
console.error('Error in handleEditUserBalance:', error);
|
||||||
await this.bot.sendMessage(chatId, 'Error loading user wallets. Please try again.');
|
await bot.sendMessage(chatId, 'Error loading user wallets. Please try again.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleBonusBalanceInput(msg) {
|
static async handleBonusBalanceInput(msg) {
|
||||||
if (!this.isAdmin(msg.from.id)) return;
|
if (!this.isAdmin(msg.from.id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const chatId = msg.chat.id;
|
const chatId = msg.chat.id;
|
||||||
const state = this.userStates.get(chatId);
|
const state = userStates.get(chatId);
|
||||||
|
|
||||||
if (!state || state.action !== 'edit_bonus_balance') {
|
if (!state || state.action !== 'edit_bonus_balance') {
|
||||||
return false;
|
return false;
|
||||||
@@ -410,17 +400,17 @@ ${purchases.map(p => ` • ${p.product_name} x${p.quantity} - $${p.total_price}
|
|||||||
const newValue = parseFloat(msg.text);
|
const newValue = parseFloat(msg.text);
|
||||||
|
|
||||||
if (isNaN(newValue)) {
|
if (isNaN(newValue)) {
|
||||||
await this.bot.sendMessage(chatId, 'Invalid value. Try again');
|
await bot.sendMessage(chatId, 'Invalid value. Try again');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await db.runAsync(`UPDATE users SET bonus_balance = ? WHERE telegram_id = ?`, [newValue, state.telegram_id])
|
await db.runAsync(`UPDATE users SET bonus_balance = ? WHERE telegram_id = ?`, [newValue, state.telegram_id])
|
||||||
await this.bot.sendMessage(chatId, '✅ Done')
|
await bot.sendMessage(chatId, '✅ Done')
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
await this.bot.sendMessage(chatId, 'Something went wrong');
|
await bot.sendMessage(chatId, 'Something went wrong');
|
||||||
}
|
}
|
||||||
|
|
||||||
this.userStates.delete(chatId);
|
userStates.delete(chatId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,27 +1,28 @@
|
|||||||
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 LocationService from "../../services/locationService.js";
|
||||||
|
import bot from "../../context/bot.js";
|
||||||
|
import UserService from "../../services/userService.js";
|
||||||
|
|
||||||
export default class AdminUserLocationHandler {
|
export default class AdminUserLocationHandler {
|
||||||
constructor(bot) {
|
static isAdmin(userId) {
|
||||||
this.bot = bot;
|
|
||||||
}
|
|
||||||
|
|
||||||
isAdmin(userId) {
|
|
||||||
return config.ADMIN_IDS.includes(userId.toString());
|
return config.ADMIN_IDS.includes(userId.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleEditUserLocation(callbackQuery) {
|
static async handleEditUserLocation(callbackQuery) {
|
||||||
if (!this.isAdmin(callbackQuery.from.id)) return;
|
if (!this.isAdmin(callbackQuery.from.id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const userId = callbackQuery.data.replace('edit_user_location_', '');
|
const userId = callbackQuery.data.replace('edit_user_location_', '');
|
||||||
const chatId = callbackQuery.message.chat.id;
|
const chatId = callbackQuery.message.chat.id;
|
||||||
const messageId = callbackQuery.message.message_id;
|
const messageId = callbackQuery.message.message_id;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const countries = await db.allAsync('SELECT DISTINCT country FROM locations ORDER BY country');
|
const countries = await LocationService.getCountries();
|
||||||
|
|
||||||
if (countries.length === 0) {
|
if (countries.length === 0) {
|
||||||
await this.bot.editMessageText(
|
await bot.editMessageText(
|
||||||
'No locations available yet.',
|
'No locations available yet.',
|
||||||
{
|
{
|
||||||
chat_id: chatId,
|
chat_id: chatId,
|
||||||
@@ -46,7 +47,7 @@ export default class AdminUserLocationHandler {
|
|||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
await this.bot.editMessageText(
|
await bot.editMessageText(
|
||||||
'🌍 Select user country:',
|
'🌍 Select user country:',
|
||||||
{
|
{
|
||||||
chat_id: chatId,
|
chat_id: chatId,
|
||||||
@@ -56,22 +57,21 @@ export default class AdminUserLocationHandler {
|
|||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in handleSetLocation:', error);
|
console.error('Error in handleSetLocation:', error);
|
||||||
await this.bot.sendMessage(chatId, 'Error loading countries. Please try again.');
|
await bot.sendMessage(chatId, 'Error loading countries. Please try again.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleEditUserCountry(callbackQuery) {
|
static async handleEditUserCountry(callbackQuery) {
|
||||||
if (!this.isAdmin(callbackQuery.from.id)) return;
|
if (!this.isAdmin(callbackQuery.from.id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const chatId = callbackQuery.message.chat.id;
|
const chatId = callbackQuery.message.chat.id;
|
||||||
const messageId = callbackQuery.message.message_id;
|
const messageId = callbackQuery.message.message_id;
|
||||||
const [country, userId] = callbackQuery.data.replace('edit_user_country_', '').split("_");
|
const [country, userId] = callbackQuery.data.replace('edit_user_country_', '').split("_");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const cities = await db.allAsync(
|
const cities = await LocationService.getCitiesByCountry(country);
|
||||||
'SELECT DISTINCT city FROM locations WHERE country = ? ORDER BY city',
|
|
||||||
[country]
|
|
||||||
);
|
|
||||||
|
|
||||||
const keyboard = {
|
const keyboard = {
|
||||||
inline_keyboard: [
|
inline_keyboard: [
|
||||||
@@ -83,7 +83,7 @@ export default class AdminUserLocationHandler {
|
|||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
await this.bot.editMessageText(
|
await bot.editMessageText(
|
||||||
`🏙 Select city in ${country}:`,
|
`🏙 Select city in ${country}:`,
|
||||||
{
|
{
|
||||||
chat_id: chatId,
|
chat_id: chatId,
|
||||||
@@ -93,22 +93,21 @@ export default class AdminUserLocationHandler {
|
|||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in handleSetCountry:', error);
|
console.error('Error in handleSetCountry:', error);
|
||||||
await this.bot.sendMessage(chatId, 'Error loading cities. Please try again.');
|
await bot.sendMessage(chatId, 'Error loading cities. Please try again.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleEditUserCity(callbackQuery) {
|
static async handleEditUserCity(callbackQuery) {
|
||||||
if (!this.isAdmin(callbackQuery.from.id)) return;
|
if (!this.isAdmin(callbackQuery.from.id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const chatId = callbackQuery.message.chat.id;
|
const chatId = callbackQuery.message.chat.id;
|
||||||
const messageId = callbackQuery.message.message_id;
|
const messageId = callbackQuery.message.message_id;
|
||||||
const [country, city, userId] = callbackQuery.data.replace('edit_user_city_', '').split('_');
|
const [country, city, userId] = callbackQuery.data.replace('edit_user_city_', '').split('_');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const districts = await db.allAsync(
|
const districts = LocationService.getDistrictsByCountryAndCity(country, city)
|
||||||
'SELECT district FROM locations WHERE country = ? AND city = ? ORDER BY district',
|
|
||||||
[country, city]
|
|
||||||
);
|
|
||||||
|
|
||||||
const keyboard = {
|
const keyboard = {
|
||||||
inline_keyboard: [
|
inline_keyboard: [
|
||||||
@@ -120,7 +119,7 @@ export default class AdminUserLocationHandler {
|
|||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
await this.bot.editMessageText(
|
await bot.editMessageText(
|
||||||
`📍 Select district in ${city}:`,
|
`📍 Select district in ${city}:`,
|
||||||
{
|
{
|
||||||
chat_id: chatId,
|
chat_id: chatId,
|
||||||
@@ -130,28 +129,25 @@ export default class AdminUserLocationHandler {
|
|||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in handleSetCity:', error);
|
console.error('Error in handleSetCity:', error);
|
||||||
await this.bot.sendMessage(chatId, 'Error loading districts. Please try again.');
|
await bot.sendMessage(chatId, 'Error loading districts. Please try again.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleEditUserDistrict(callbackQuery) {
|
static async handleEditUserDistrict(callbackQuery) {
|
||||||
if (!this.isAdmin(callbackQuery.from.id)) return;
|
if (!this.isAdmin(callbackQuery.from.id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const chatId = callbackQuery.message.chat.id;
|
const chatId = callbackQuery.message.chat.id;
|
||||||
const messageId = callbackQuery.message.message_id;
|
const messageId = callbackQuery.message.message_id;
|
||||||
const [country, city, district, userId] = callbackQuery.data.replace('edit_user_district_', '').split('_');
|
const [country, city, district, telegramId] = callbackQuery.data.replace('edit_user_district_', '').split('_');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await db.runAsync('BEGIN TRANSACTION');
|
await db.runAsync('BEGIN TRANSACTION');
|
||||||
|
await UserService.updateUserLocation(telegramId.toString(), country, city, district)
|
||||||
await db.runAsync(
|
|
||||||
'UPDATE users SET country = ?, city = ?, district = ? WHERE telegram_id = ?',
|
|
||||||
[country, city, district, userId.toString()]
|
|
||||||
);
|
|
||||||
|
|
||||||
await db.runAsync('COMMIT');
|
await db.runAsync('COMMIT');
|
||||||
|
|
||||||
await this.bot.editMessageText(
|
await bot.editMessageText(
|
||||||
`✅ Location updated successfully!\n\nCountry: ${country}\nCity: ${city}\nDistrict: ${district}`,
|
`✅ Location updated successfully!\n\nCountry: ${country}\nCity: ${city}\nDistrict: ${district}`,
|
||||||
{
|
{
|
||||||
chat_id: chatId,
|
chat_id: chatId,
|
||||||
@@ -166,7 +162,7 @@ export default class AdminUserLocationHandler {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
await db.runAsync('ROLLBACK');
|
await db.runAsync('ROLLBACK');
|
||||||
console.error('Error in handleSetDistrict:', error);
|
console.error('Error in handleSetDistrict:', error);
|
||||||
await this.bot.sendMessage(chatId, 'Error updating location. Please try again.');
|
await bot.sendMessage(chatId, 'Error updating location. Please try again.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,14 +1,11 @@
|
|||||||
import User from '../models/User.js';
|
import config from "../../config/config.js";
|
||||||
import config from "../config/config.js";
|
import bot from "../../context/bot.js";
|
||||||
|
import UserService from "../../services/userService.js";
|
||||||
|
|
||||||
export default class UserHandler {
|
export default class UserHandler {
|
||||||
constructor(bot) {
|
static async canUseBot(msg) {
|
||||||
this.bot = bot;
|
const telegramId = msg.from.id;
|
||||||
}
|
const user = await UserService.getUserByTelegramId(telegramId);
|
||||||
|
|
||||||
async canUseBot(msg) {
|
|
||||||
const userId = msg.from.id;
|
|
||||||
const user = await User.getById(userId);
|
|
||||||
|
|
||||||
const keyboard = {
|
const keyboard = {
|
||||||
inline_keyboard: [
|
inline_keyboard: [
|
||||||
@@ -20,26 +17,26 @@ export default class UserHandler {
|
|||||||
case 0:
|
case 0:
|
||||||
return true;
|
return true;
|
||||||
case 1:
|
case 1:
|
||||||
await this.bot.sendMessage(userId, '⚠️Your account has been deleted by administrator', {reply_markup: keyboard});
|
await bot.sendMessage(telegramId, '⚠️Your account has been deleted by administrator', {reply_markup: keyboard});
|
||||||
return false;
|
return false;
|
||||||
case 2:
|
case 2:
|
||||||
await this.bot.sendMessage(userId, '⚠️Your account has been blocked by administrator', {reply_markup: keyboard});
|
await bot.sendMessage(telegramId, '⚠️Your account has been blocked by administrator', {reply_markup: keyboard});
|
||||||
return false;
|
return false;
|
||||||
default:
|
default:
|
||||||
return true
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async showProfile(msg) {
|
static async showProfile(msg) {
|
||||||
const chatId = msg.chat.id;
|
const chatId = msg.chat.id;
|
||||||
const userId = msg.from.id;
|
const telegramId = msg.from.id;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await User.recalculateBalance(userId);
|
await UserService.recalculateUserBalanceByTelegramId(telegramId);
|
||||||
const userStats = await User.getUserStats(userId);
|
const userStats = await UserService.getDetailedUserByTelegramId(telegramId);
|
||||||
|
|
||||||
if (!userStats) {
|
if (!userStats) {
|
||||||
await this.bot.sendMessage(chatId, 'Profile not found. Please use /start to create one.');
|
await bot.sendMessage(chatId, 'Profile not found. Please use /start to create one.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,7 +47,7 @@ export default class UserHandler {
|
|||||||
const text = `
|
const text = `
|
||||||
👤 *Your Profile*
|
👤 *Your Profile*
|
||||||
|
|
||||||
📱 Telegram ID: \`${userId}\`
|
📱 Telegram ID: \`${telegramId}\`
|
||||||
📍 Location: ${locationText}
|
📍 Location: ${locationText}
|
||||||
|
|
||||||
📊 Statistics:
|
📊 Statistics:
|
||||||
@@ -70,24 +67,27 @@ export default class UserHandler {
|
|||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
await this.bot.sendMessage(chatId, text, {
|
await bot.sendMessage(chatId, text, {
|
||||||
parse_mode: 'Markdown',
|
parse_mode: 'Markdown',
|
||||||
reply_markup: keyboard
|
reply_markup: keyboard
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in showProfile:', error);
|
console.error('Error in showProfile:', error);
|
||||||
await this.bot.sendMessage(chatId, 'Error loading profile. Please try again.');
|
await bot.sendMessage(chatId, 'Error loading profile. Please try again.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleStart(msg) {
|
static async handleStart(msg) {
|
||||||
const chatId = msg.chat.id;
|
const chatId = msg.chat.id;
|
||||||
const userId = msg.from.id;
|
const telegramId = msg.from.id;
|
||||||
const username = msg.chat.username;
|
const username = msg.chat.username;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Create user profile
|
// Create user profile
|
||||||
await User.create(userId, username);
|
await UserService.createUser({
|
||||||
|
telegram_id: telegramId,
|
||||||
|
username: username
|
||||||
|
});
|
||||||
|
|
||||||
const keyboard = {
|
const keyboard = {
|
||||||
reply_markup: {
|
reply_markup: {
|
||||||
@@ -99,22 +99,22 @@ export default class UserHandler {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
await this.bot.sendMessage(
|
await bot.sendMessage(
|
||||||
chatId,
|
chatId,
|
||||||
'Welcome to the shop! Choose an option:',
|
'Welcome to the shop! Choose an option:',
|
||||||
keyboard
|
keyboard
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in handleStart:', error);
|
console.error('Error in handleStart:', error);
|
||||||
await this.bot.sendMessage(chatId, 'Error creating user profile. Please try again.');
|
await bot.sendMessage(chatId, 'Error creating user profile. Please try again.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleBackToProfile(callbackQuery) {
|
static async handleBackToProfile(callbackQuery) {
|
||||||
await this.showProfile({
|
await this.showProfile({
|
||||||
chat: {id: callbackQuery.message.chat.id},
|
chat: {id: callbackQuery.message.chat.id},
|
||||||
from: {id: callbackQuery.from.id}
|
from: {id: callbackQuery.from.id}
|
||||||
});
|
});
|
||||||
await this.bot.deleteMessage(callbackQuery.message.chat.id, callbackQuery.message.message_id);
|
await bot.deleteMessage(callbackQuery.message.chat.id, callbackQuery.message.message_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
147
src/handlers/userHandlers/userLocationHandler.js
Normal file
147
src/handlers/userHandlers/userLocationHandler.js
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
import db from '../../config/database.js';
|
||||||
|
import LocationService from "../../services/locationService.js";
|
||||||
|
import bot from "../../context/bot.js";
|
||||||
|
import UserService from "../../services/userService.js";
|
||||||
|
|
||||||
|
export default class UserLocationHandler {
|
||||||
|
static async handleSetLocation(callbackQuery) {
|
||||||
|
const chatId = callbackQuery.message.chat.id;
|
||||||
|
const messageId = callbackQuery.message.message_id;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const countries = await LocationService.getCountries();
|
||||||
|
|
||||||
|
if (countries.length === 0) {
|
||||||
|
await bot.editMessageText(
|
||||||
|
'No locations available yet.',
|
||||||
|
{
|
||||||
|
chat_id: chatId,
|
||||||
|
message_id: messageId,
|
||||||
|
reply_markup: {
|
||||||
|
inline_keyboard: [[
|
||||||
|
{text: '« Back to Profile', callback_data: 'back_to_profile'}
|
||||||
|
]]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const keyboard = {
|
||||||
|
inline_keyboard: [
|
||||||
|
...countries.map(loc => [{
|
||||||
|
text: loc.country,
|
||||||
|
callback_data: `set_country_${loc.country}`
|
||||||
|
}]),
|
||||||
|
[{text: '« Back to Profile', callback_data: 'back_to_profile'}]
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
await bot.editMessageText(
|
||||||
|
'🌍 Select your country:',
|
||||||
|
{
|
||||||
|
chat_id: chatId,
|
||||||
|
message_id: messageId,
|
||||||
|
reply_markup: keyboard
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in handleSetLocation:', error);
|
||||||
|
await bot.sendMessage(chatId, 'Error loading countries. Please try again.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async handleSetCountry(callbackQuery) {
|
||||||
|
const chatId = callbackQuery.message.chat.id;
|
||||||
|
const messageId = callbackQuery.message.message_id;
|
||||||
|
const country = callbackQuery.data.replace('set_country_', '');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const cities = await LocationService.getCitiesByCountry(country);
|
||||||
|
|
||||||
|
const keyboard = {
|
||||||
|
inline_keyboard: [
|
||||||
|
...cities.map(loc => [{
|
||||||
|
text: loc.city,
|
||||||
|
callback_data: `set_city_${country}_${loc.city}`
|
||||||
|
}]),
|
||||||
|
[{text: '« Back to Countries', callback_data: 'set_location'}]
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
await bot.editMessageText(
|
||||||
|
`🏙 Select city in ${country}:`,
|
||||||
|
{
|
||||||
|
chat_id: chatId,
|
||||||
|
message_id: messageId,
|
||||||
|
reply_markup: keyboard
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in handleSetCountry:', error);
|
||||||
|
await bot.sendMessage(chatId, 'Error loading cities. Please try again.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async handleSetCity(callbackQuery) {
|
||||||
|
const chatId = callbackQuery.message.chat.id;
|
||||||
|
const messageId = callbackQuery.message.message_id;
|
||||||
|
const [country, city] = callbackQuery.data.replace('set_city_', '').split('_');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const districts = await LocationService.getDistrictsByCountryAndCity(country, city);
|
||||||
|
|
||||||
|
const keyboard = {
|
||||||
|
inline_keyboard: [
|
||||||
|
...districts.map(loc => [{
|
||||||
|
text: loc.district,
|
||||||
|
callback_data: `set_district_${country}_${city}_${loc.district}`
|
||||||
|
}]),
|
||||||
|
[{text: '« Back to Cities', callback_data: `set_country_${country}`}]
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
await bot.editMessageText(
|
||||||
|
`📍 Select district in ${city}:`,
|
||||||
|
{
|
||||||
|
chat_id: chatId,
|
||||||
|
message_id: messageId,
|
||||||
|
reply_markup: keyboard
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in handleSetCity:', error);
|
||||||
|
await bot.sendMessage(chatId, 'Error loading districts. Please try again.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async handleSetDistrict(callbackQuery) {
|
||||||
|
const chatId = callbackQuery.message.chat.id;
|
||||||
|
const messageId = callbackQuery.message.message_id;
|
||||||
|
const telegramId = callbackQuery.from.id;
|
||||||
|
const [country, city, district] = callbackQuery.data.replace('set_district_', '').split('_');
|
||||||
|
|
||||||
|
try {
|
||||||
|
await db.runAsync('BEGIN TRANSACTION');
|
||||||
|
await UserService.updateUserLocation(telegramId, country, city, district);
|
||||||
|
await db.runAsync('COMMIT');
|
||||||
|
|
||||||
|
await bot.editMessageText(
|
||||||
|
`✅ Location updated successfully!\n\nCountry: ${country}\nCity: ${city}\nDistrict: ${district}`,
|
||||||
|
{
|
||||||
|
chat_id: chatId,
|
||||||
|
message_id: messageId,
|
||||||
|
reply_markup: {
|
||||||
|
inline_keyboard: [[
|
||||||
|
{text: '« Back to Profile', callback_data: 'back_to_profile'}
|
||||||
|
]]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
await db.runAsync('ROLLBACK');
|
||||||
|
console.error('Error in handleSetDistrict:', error);
|
||||||
|
await bot.sendMessage(chatId, 'Error updating location. Please try again.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,32 +1,30 @@
|
|||||||
import db from '../config/database.js';
|
import db from '../../config/database.js';
|
||||||
import User from '../models/User.js';
|
import config from "../../config/config.js";
|
||||||
import WalletService from "../utils/walletService.js";
|
import LocationService from "../../services/locationService.js";
|
||||||
import config from "../config/config.js";
|
import bot from "../../context/bot.js";
|
||||||
|
import userStates from "../../context/userStates.js";
|
||||||
|
import ProductService from "../../services/productService.js";
|
||||||
|
import CategoryService from "../../services/categoryService.js";
|
||||||
|
import UserService from "../../services/userService.js";
|
||||||
|
import PurchaseService from "../../services/purchaseService.js";
|
||||||
|
|
||||||
export default class UserProductHandler {
|
export default class UserProductHandler {
|
||||||
constructor(bot) {
|
static async showProducts(msg) {
|
||||||
this.bot = bot;
|
|
||||||
this.userStates = new Map();
|
|
||||||
}
|
|
||||||
|
|
||||||
async showProducts(msg) {
|
|
||||||
const chatId = msg.chat.id;
|
const chatId = msg.chat.id;
|
||||||
const messageId = msg?.message_id;
|
const messageId = msg?.message_id;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const countries = await db.allAsync(
|
const countries = await LocationService.getCountries()
|
||||||
'SELECT DISTINCT country FROM locations ORDER BY country'
|
|
||||||
);
|
|
||||||
|
|
||||||
if (countries.length === 0) {
|
if (countries.length === 0) {
|
||||||
const message = 'No products available at the moment.';
|
const message = 'No products available at the moment.';
|
||||||
if (messageId) {
|
if (messageId) {
|
||||||
await this.bot.editMessageText(message, {
|
await bot.editMessageText(message, {
|
||||||
chat_id: chatId,
|
chat_id: chatId,
|
||||||
message_id: messageId
|
message_id: messageId
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
await this.bot.sendMessage(chatId, message);
|
await bot.sendMessage(chatId, message);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -42,31 +40,28 @@ export default class UserProductHandler {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (messageId) {
|
if (messageId) {
|
||||||
await this.bot.editMessageText(message, {
|
await bot.editMessageText(message, {
|
||||||
chat_id: chatId,
|
chat_id: chatId,
|
||||||
message_id: messageId,
|
message_id: messageId,
|
||||||
reply_markup: keyboard
|
reply_markup: keyboard
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await this.bot.sendMessage(chatId, message, {reply_markup: keyboard});
|
await bot.sendMessage(chatId, message, {reply_markup: keyboard});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in showProducts:', error);
|
console.error('Error in showProducts:', error);
|
||||||
await this.bot.sendMessage(chatId, 'Error loading products. Please try again.');
|
await bot.sendMessage(chatId, 'Error loading products. Please try again.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleCountrySelection(callbackQuery) {
|
static async handleCountrySelection(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;
|
||||||
const country = callbackQuery.data.replace('shop_country_', '');
|
const country = callbackQuery.data.replace('shop_country_', '');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const cities = await db.allAsync(
|
const cities = await LocationService.getCitiesByCountry(country);
|
||||||
'SELECT DISTINCT city FROM locations WHERE country = ? ORDER BY city',
|
|
||||||
[country]
|
|
||||||
);
|
|
||||||
|
|
||||||
const keyboard = {
|
const keyboard = {
|
||||||
inline_keyboard: [
|
inline_keyboard: [
|
||||||
@@ -78,7 +73,7 @@ export default class UserProductHandler {
|
|||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
await this.bot.editMessageText(
|
await bot.editMessageText(
|
||||||
`🏙 Select city in ${country}:`,
|
`🏙 Select city in ${country}:`,
|
||||||
{
|
{
|
||||||
chat_id: chatId,
|
chat_id: chatId,
|
||||||
@@ -88,20 +83,17 @@ export default class UserProductHandler {
|
|||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in handleCountrySelection:', error);
|
console.error('Error in handleCountrySelection:', error);
|
||||||
await this.bot.sendMessage(chatId, 'Error loading cities. Please try again.');
|
await bot.sendMessage(chatId, 'Error loading cities. Please try again.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleCitySelection(callbackQuery) {
|
static async handleCitySelection(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;
|
||||||
const [country, city] = callbackQuery.data.replace('shop_city_', '').split('_');
|
const [country, city] = callbackQuery.data.replace('shop_city_', '').split('_');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const districts = await db.allAsync(
|
const districts = await LocationService.getDistrictsByCountryAndCity(country, city)
|
||||||
'SELECT district FROM locations WHERE country = ? AND city = ? ORDER BY district',
|
|
||||||
[country, city]
|
|
||||||
);
|
|
||||||
|
|
||||||
const keyboard = {
|
const keyboard = {
|
||||||
inline_keyboard: [
|
inline_keyboard: [
|
||||||
@@ -113,7 +105,7 @@ export default class UserProductHandler {
|
|||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
await this.bot.editMessageText(
|
await bot.editMessageText(
|
||||||
`📍 Select district in ${city}:`,
|
`📍 Select district in ${city}:`,
|
||||||
{
|
{
|
||||||
chat_id: chatId,
|
chat_id: chatId,
|
||||||
@@ -123,32 +115,26 @@ export default class UserProductHandler {
|
|||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in handleCitySelection:', error);
|
console.error('Error in handleCitySelection:', error);
|
||||||
await this.bot.sendMessage(chatId, 'Error loading districts. Please try again.');
|
await bot.sendMessage(chatId, 'Error loading districts. Please try again.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleDistrictSelection(callbackQuery) {
|
static async handleDistrictSelection(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;
|
||||||
const [country, city, district] = callbackQuery.data.replace('shop_district_', '').split('_');
|
const [country, city, district] = callbackQuery.data.replace('shop_district_', '').split('_');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const location = await db.getAsync(
|
const location = await LocationService.getLocation(country, city, district);
|
||||||
'SELECT id FROM locations WHERE country = ? AND city = ? AND district = ?',
|
|
||||||
[country, city, district]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!location) {
|
if (!location) {
|
||||||
throw new Error('Location not found');
|
throw new Error('Location not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
const categories = await db.allAsync(
|
const categories = await CategoryService.getCategoriesByLocationId(location.id);
|
||||||
'SELECT id, name FROM categories WHERE location_id = ? ORDER BY name',
|
|
||||||
[location.id]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (categories.length === 0) {
|
if (categories.length === 0) {
|
||||||
await this.bot.editMessageText(
|
await bot.editMessageText(
|
||||||
'No products available in this location yet.',
|
'No products available in this location yet.',
|
||||||
{
|
{
|
||||||
chat_id: chatId,
|
chat_id: chatId,
|
||||||
@@ -173,7 +159,7 @@ export default class UserProductHandler {
|
|||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
await this.bot.editMessageText(
|
await bot.editMessageText(
|
||||||
'📦 Select category:',
|
'📦 Select category:',
|
||||||
{
|
{
|
||||||
chat_id: chatId,
|
chat_id: chatId,
|
||||||
@@ -183,28 +169,21 @@ export default class UserProductHandler {
|
|||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in handleDistrictSelection:', error);
|
console.error('Error in handleDistrictSelection:', error);
|
||||||
await this.bot.sendMessage(chatId, 'Error loading categories. Please try again.');
|
await bot.sendMessage(chatId, 'Error loading categories. Please try again.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleCategorySelection(callbackQuery) {
|
static async handleCategorySelection(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;
|
||||||
const [locationId, categoryId] = callbackQuery.data.replace('shop_category_', '').split('_');
|
const [locationId, categoryId] = callbackQuery.data.replace('shop_category_', '').split('_');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const subcategories = await db.allAsync(
|
const subcategories = await CategoryService.getSubcategoriesByCategoryId(categoryId);
|
||||||
'SELECT id, name FROM subcategories WHERE category_id = ? ORDER BY name',
|
const location = await LocationService.getLocationById(locationId);
|
||||||
[categoryId]
|
|
||||||
);
|
|
||||||
|
|
||||||
const location = await db.getAsync(
|
|
||||||
'SELECT country, city, district FROM locations WHERE id = ?',
|
|
||||||
[locationId]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (subcategories.length === 0) {
|
if (subcategories.length === 0) {
|
||||||
await this.bot.editMessageText(
|
await bot.editMessageText(
|
||||||
'No products available in this category yet.',
|
'No products available in this category yet.',
|
||||||
{
|
{
|
||||||
chat_id: chatId,
|
chat_id: chatId,
|
||||||
@@ -235,7 +214,7 @@ export default class UserProductHandler {
|
|||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
await this.bot.editMessageText(
|
await bot.editMessageText(
|
||||||
'📦 Select subcategory:',
|
'📦 Select subcategory:',
|
||||||
{
|
{
|
||||||
chat_id: chatId,
|
chat_id: chatId,
|
||||||
@@ -245,11 +224,11 @@ export default class UserProductHandler {
|
|||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in handleCategorySelection:', error);
|
console.error('Error in handleCategorySelection:', error);
|
||||||
await this.bot.sendMessage(chatId, 'Error loading subcategories. Please try again.');
|
await bot.sendMessage(chatId, 'Error loading subcategories. Please try again.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleSubcategorySelection(callbackQuery) {
|
static async handleSubcategorySelection(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;
|
||||||
const [locationId, categoryId, subcategoryId, photoMessageId] = callbackQuery.data.replace('shop_subcategory_', '').split('_');
|
const [locationId, categoryId, subcategoryId, photoMessageId] = callbackQuery.data.replace('shop_subcategory_', '').split('_');
|
||||||
@@ -258,27 +237,17 @@ export default class UserProductHandler {
|
|||||||
// Delete the photo message if it exists
|
// Delete the photo message if it exists
|
||||||
if (photoMessageId) {
|
if (photoMessageId) {
|
||||||
try {
|
try {
|
||||||
await this.bot.deleteMessage(chatId, photoMessageId);
|
await bot.deleteMessage(chatId, photoMessageId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error deleting photo message:', error);
|
console.error('Error deleting photo message:', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const products = await db.allAsync(
|
const products = await ProductService.getProductsByLocationAndCategory(locationId, categoryId, subcategoryId);
|
||||||
`SELECT id, name, price, description, quantity_in_stock, photo_url
|
const subcategory = await CategoryService.getSubcategoryById(subcategoryId);
|
||||||
FROM products
|
|
||||||
WHERE location_id = ? AND category_id = ? AND subcategory_id = ?
|
|
||||||
AND quantity_in_stock > 0
|
|
||||||
ORDER BY name`,
|
|
||||||
[locationId, categoryId, subcategoryId]
|
|
||||||
);
|
|
||||||
|
|
||||||
const location = await db.getAsync('SELECT * 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]);
|
|
||||||
|
|
||||||
if (products.length === 0) {
|
if (products.length === 0) {
|
||||||
await this.bot.editMessageText(
|
await bot.editMessageText(
|
||||||
'No products available in this subcategory.',
|
'No products available in this subcategory.',
|
||||||
{
|
{
|
||||||
chat_id: chatId,
|
chat_id: chatId,
|
||||||
@@ -306,7 +275,7 @@ export default class UserProductHandler {
|
|||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
await this.bot.editMessageText(
|
await bot.editMessageText(
|
||||||
`📦 Products in ${subcategory.name}:`,
|
`📦 Products in ${subcategory.name}:`,
|
||||||
{
|
{
|
||||||
chat_id: chatId,
|
chat_id: chatId,
|
||||||
@@ -316,31 +285,24 @@ export default class UserProductHandler {
|
|||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in handleSubcategorySelection:', error);
|
console.error('Error in handleSubcategorySelection:', error);
|
||||||
await this.bot.sendMessage(chatId, 'Error loading products. Please try again.');
|
await bot.sendMessage(chatId, 'Error loading products. Please try again.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleProductSelection(callbackQuery) {
|
static async handleProductSelection(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;
|
||||||
const productId = callbackQuery.data.replace('shop_product_', '');
|
const productId = callbackQuery.data.replace('shop_product_', '');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const product = await db.getAsync(
|
const product = await ProductService.getDetailedProductById(productId);
|
||||||
`SELECT p.*, c.name as category_name, s.name as subcategory_name
|
|
||||||
FROM products p
|
|
||||||
JOIN categories c ON p.category_id = c.id
|
|
||||||
JOIN subcategories s ON p.subcategory_id = s.id
|
|
||||||
WHERE p.id = ?`,
|
|
||||||
[productId]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!product) {
|
if (!product) {
|
||||||
throw new Error('Product not found');
|
throw new Error('Product not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete the previous message
|
// Delete the previous message
|
||||||
await this.bot.deleteMessage(chatId, messageId);
|
await bot.deleteMessage(chatId, messageId);
|
||||||
|
|
||||||
const message = `
|
const message = `
|
||||||
📦 ${product.name}
|
📦 ${product.name}
|
||||||
@@ -359,9 +321,9 @@ Subcategory: ${product.subcategory_name}
|
|||||||
let photoMessage;
|
let photoMessage;
|
||||||
if (product.photo_url) {
|
if (product.photo_url) {
|
||||||
try {
|
try {
|
||||||
photoMessage = await this.bot.sendPhoto(chatId, product.photo_url, {caption: 'Public photo'});
|
photoMessage = await bot.sendPhoto(chatId, product.photo_url, {caption: 'Public photo'});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
photoMessage = await this.bot.sendPhoto(chatId, "./corrupt-photo.jpg", {caption: 'Public photo'})
|
photoMessage = await bot.sendPhoto(chatId, "./corrupt-photo.jpg", {caption: 'Public photo'})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -389,13 +351,13 @@ Subcategory: ${product.subcategory_name}
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Then send the message with controls
|
// Then send the message with controls
|
||||||
await this.bot.sendMessage(chatId, message, {
|
await bot.sendMessage(chatId, message, {
|
||||||
reply_markup: keyboard,
|
reply_markup: keyboard,
|
||||||
parse_mode: 'HTML'
|
parse_mode: 'HTML'
|
||||||
});
|
});
|
||||||
|
|
||||||
// Store the current quantity and photo message ID in user state
|
// Store the current quantity and photo message ID in user state
|
||||||
this.userStates.set(chatId, {
|
userStates.set(chatId, {
|
||||||
action: 'buying_product',
|
action: 'buying_product',
|
||||||
productId,
|
productId,
|
||||||
quantity: 1,
|
quantity: 1,
|
||||||
@@ -403,21 +365,18 @@ Subcategory: ${product.subcategory_name}
|
|||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in handleProductSelection:', error);
|
console.error('Error in handleProductSelection:', error);
|
||||||
await this.bot.sendMessage(chatId, 'Error loading product details. Please try again.');
|
await bot.sendMessage(chatId, 'Error loading product details. Please try again.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleIncreaseQuantity(callbackQuery) {
|
static async handleIncreaseQuantity(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;
|
||||||
const productId = callbackQuery.data.replace('increase_quantity_', '');
|
const productId = callbackQuery.data.replace('increase_quantity_', '');
|
||||||
const state = this.userStates.get(chatId);
|
const state = userStates.get(chatId);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const product = await db.getAsync(
|
const product = await ProductService.getProductById(productId);
|
||||||
'SELECT quantity_in_stock FROM products WHERE id = ?',
|
|
||||||
[productId]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!product) {
|
if (!product) {
|
||||||
throw new Error('Product not found');
|
throw new Error('Product not found');
|
||||||
@@ -427,14 +386,14 @@ Subcategory: ${product.subcategory_name}
|
|||||||
|
|
||||||
// If already at max stock, silently ignore
|
// If already at max stock, silently ignore
|
||||||
if (currentQuantity >= product.quantity_in_stock) {
|
if (currentQuantity >= product.quantity_in_stock) {
|
||||||
await this.bot.answerCallbackQuery(callbackQuery.id);
|
await bot.answerCallbackQuery(callbackQuery.id);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const newQuantity = Math.min(currentQuantity + 1, product.quantity_in_stock);
|
const newQuantity = Math.min(currentQuantity + 1, product.quantity_in_stock);
|
||||||
|
|
||||||
// Update state
|
// Update state
|
||||||
this.userStates.set(chatId, {
|
userStates.set(chatId, {
|
||||||
...state,
|
...state,
|
||||||
quantity: newQuantity
|
quantity: newQuantity
|
||||||
});
|
});
|
||||||
@@ -455,7 +414,7 @@ Subcategory: ${product.subcategory_name}
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
await this.bot.editMessageReplyMarkup(
|
await bot.editMessageReplyMarkup(
|
||||||
{inline_keyboard: keyboard},
|
{inline_keyboard: keyboard},
|
||||||
{
|
{
|
||||||
chat_id: chatId,
|
chat_id: chatId,
|
||||||
@@ -463,24 +422,21 @@ Subcategory: ${product.subcategory_name}
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
await this.bot.answerCallbackQuery(callbackQuery.id);
|
await bot.answerCallbackQuery(callbackQuery.id);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in handleIncreaseQuantity:', error);
|
console.error('Error in handleIncreaseQuantity:', error);
|
||||||
await this.bot.answerCallbackQuery(callbackQuery.id);
|
await bot.answerCallbackQuery(callbackQuery.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleDecreaseQuantity(callbackQuery) {
|
static async handleDecreaseQuantity(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;
|
||||||
const productId = callbackQuery.data.replace('decrease_quantity_', '');
|
const productId = callbackQuery.data.replace('decrease_quantity_', '');
|
||||||
const state = this.userStates.get(chatId);
|
const state = userStates.get(chatId);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const product = await db.getAsync(
|
const product = await ProductService.getProductById(productId)
|
||||||
'SELECT quantity_in_stock FROM products WHERE id = ?',
|
|
||||||
[productId]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!product) {
|
if (!product) {
|
||||||
throw new Error('Product not found');
|
throw new Error('Product not found');
|
||||||
@@ -490,14 +446,14 @@ Subcategory: ${product.subcategory_name}
|
|||||||
|
|
||||||
// If already at minimum, silently ignore
|
// If already at minimum, silently ignore
|
||||||
if (currentQuantity <= 1) {
|
if (currentQuantity <= 1) {
|
||||||
await this.bot.answerCallbackQuery(callbackQuery.id);
|
await bot.answerCallbackQuery(callbackQuery.id);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const newQuantity = Math.max(currentQuantity - 1, 1);
|
const newQuantity = Math.max(currentQuantity - 1, 1);
|
||||||
|
|
||||||
// Update state
|
// Update state
|
||||||
this.userStates.set(chatId, {
|
userStates.set(chatId, {
|
||||||
...state,
|
...state,
|
||||||
quantity: newQuantity
|
quantity: newQuantity
|
||||||
});
|
});
|
||||||
@@ -518,7 +474,7 @@ Subcategory: ${product.subcategory_name}
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
await this.bot.editMessageReplyMarkup(
|
await bot.editMessageReplyMarkup(
|
||||||
{inline_keyboard: keyboard},
|
{inline_keyboard: keyboard},
|
||||||
{
|
{
|
||||||
chat_id: chatId,
|
chat_id: chatId,
|
||||||
@@ -526,29 +482,26 @@ Subcategory: ${product.subcategory_name}
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
await this.bot.answerCallbackQuery(callbackQuery.id);
|
await bot.answerCallbackQuery(callbackQuery.id);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in handleDecreaseQuantity:', error);
|
console.error('Error in handleDecreaseQuantity:', error);
|
||||||
await this.bot.answerCallbackQuery(callbackQuery.id);
|
await bot.answerCallbackQuery(callbackQuery.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleBuyProduct(callbackQuery) {
|
static async handleBuyProduct(callbackQuery) {
|
||||||
const chatId = callbackQuery.message.chat.id;
|
const chatId = callbackQuery.message.chat.id;
|
||||||
const userId = callbackQuery.from.id;
|
const telegramId = callbackQuery.from.id;
|
||||||
const productId = callbackQuery.data.replace('buy_product_', '');
|
const productId = callbackQuery.data.replace('buy_product_', '');
|
||||||
const state = this.userStates.get(chatId);
|
const state = userStates.get(chatId);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const user = await User.getById(userId);
|
const user = await UserService.getUserByTelegramId(telegramId)
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw new Error('User not found');
|
throw new Error('User not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
const product = await db.getAsync(
|
const product = await ProductService.getProductById(productId);
|
||||||
'SELECT * FROM products WHERE id = ?',
|
|
||||||
[productId]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!product) {
|
if (!product) {
|
||||||
throw new Error('Product not found');
|
throw new Error('Product not found');
|
||||||
@@ -566,7 +519,7 @@ Subcategory: ${product.subcategory_name}
|
|||||||
`, [user.id]);
|
`, [user.id]);
|
||||||
|
|
||||||
if (cryptoWallets.length === 0) {
|
if (cryptoWallets.length === 0) {
|
||||||
await this.bot.sendMessage(
|
await bot.sendMessage(
|
||||||
chatId,
|
chatId,
|
||||||
'You need to add a crypto wallet first to make purchases.',
|
'You need to add a crypto wallet first to make purchases.',
|
||||||
{
|
{
|
||||||
@@ -590,7 +543,7 @@ Subcategory: ${product.subcategory_name}
|
|||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
await this.bot.editMessageText(
|
await bot.editMessageText(
|
||||||
`🛒 Purchase Summary:\n\n` +
|
`🛒 Purchase Summary:\n\n` +
|
||||||
`Product: ${product.name}\n` +
|
`Product: ${product.name}\n` +
|
||||||
`Quantity: ${quantity}\n` +
|
`Quantity: ${quantity}\n` +
|
||||||
@@ -604,29 +557,25 @@ Subcategory: ${product.subcategory_name}
|
|||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in handleBuyProduct:', error);
|
console.error('Error in handleBuyProduct:', error);
|
||||||
await this.bot.sendMessage(chatId, 'Error processing purchase. Please try again.');
|
await bot.sendMessage(chatId, 'Error processing purchase. Please try again.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handlePay(callbackQuery) {
|
static async handlePay(callbackQuery) {
|
||||||
const chatId = callbackQuery.message.chat.id;
|
const chatId = callbackQuery.message.chat.id;
|
||||||
const userId = callbackQuery.from.id;
|
const telegramId = callbackQuery.from.id;
|
||||||
const [walletType, productId, quantity] = callbackQuery.data.replace('pay_with_', '').split('_');
|
const [walletType, productId, quantity] = callbackQuery.data.replace('pay_with_', '').split('_');
|
||||||
const state = this.userStates.get(chatId);
|
const state = userStates.get(chatId);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await User.recalculateBalance(userId);
|
await UserService.recalculateUserBalanceByTelegramId(telegramId);
|
||||||
const user = await User.getById(userId);
|
const user = await UserService.getUserByTelegramId(telegramId)
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw new Error('User not found');
|
throw new Error('User not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
const product = await db.getAsync(
|
const product = await ProductService.getProductById(productId);
|
||||||
'SELECT * FROM products WHERE id = ?',
|
|
||||||
[productId]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!product) {
|
if (!product) {
|
||||||
throw new Error('Product not found');
|
throw new Error('Product not found');
|
||||||
}
|
}
|
||||||
@@ -635,25 +584,22 @@ Subcategory: ${product.subcategory_name}
|
|||||||
const balance = user.total_balance + user.bonus_balance;
|
const balance = user.total_balance + user.bonus_balance;
|
||||||
|
|
||||||
if (totalPrice > balance) {
|
if (totalPrice > balance) {
|
||||||
this.userStates.delete(chatId);
|
userStates.delete(chatId);
|
||||||
await this.bot.editMessageText(`Not enough money`, {
|
await bot.editMessageText(`Not enough money`, {
|
||||||
chat_id: chatId,
|
chat_id: chatId,
|
||||||
message_id: callbackQuery.message.message_id,
|
message_id: callbackQuery.message.message_id,
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await db.runAsync(
|
await PurchaseService.createPurchase(user.id, product.id, walletType, quantity, totalPrice)
|
||||||
'INSERT INTO purchases (user_id, product_id, wallet_type, tx_hash, quantity, total_price) VALUES (?, ?, ?, ?, ?, ?)',
|
|
||||||
[user.id, product.id, walletType, "null", quantity, totalPrice]
|
|
||||||
);
|
|
||||||
|
|
||||||
let hiddenPhotoMessage;
|
let hiddenPhotoMessage;
|
||||||
if (product.hidden_photo_url) {
|
if (product.hidden_photo_url) {
|
||||||
try {
|
try {
|
||||||
hiddenPhotoMessage = await this.bot.sendPhoto(chatId, product.hidden_photo_url, {caption: 'Hidden photo'});
|
hiddenPhotoMessage = await bot.sendPhoto(chatId, product.hidden_photo_url, {caption: 'Hidden photo'});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
hiddenPhotoMessage = await this.bot.sendPhoto(chatId, "./corrupt-photo.jpg", {caption: 'Hidden photo'})
|
hiddenPhotoMessage = await bot.sendPhoto(chatId, "./corrupt-photo.jpg", {caption: 'Hidden photo'})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -681,11 +627,11 @@ Coordinates: ${product.hidden_coordinates}
|
|||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
await this.bot.sendMessage(chatId, message, {reply_markup: keyboard});
|
await bot.sendMessage(chatId, message, {reply_markup: keyboard});
|
||||||
await this.bot.deleteMessage(chatId, callbackQuery.message.message_id);
|
await bot.deleteMessage(chatId, callbackQuery.message.message_id);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in handleBuyProduct:', error);
|
console.error('Error in handleBuyProduct:', error);
|
||||||
await this.bot.sendMessage(chatId, 'Error processing purchase. Please try again.');
|
await bot.sendMessage(chatId, 'Error processing purchase. Please try again.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,15 +1,11 @@
|
|||||||
import db from '../config/database.js';
|
import config from "../../config/config.js";
|
||||||
import User from '../models/User.js';
|
import PurchaseService from "../../services/purchaseService.js";
|
||||||
import WalletService from "../utils/walletService.js";
|
import UserService from "../../services/userService.js";
|
||||||
import config from "../config/config.js";
|
import bot from "../../context/bot.js";
|
||||||
|
import ProductService from "../../services/productService.js";
|
||||||
|
|
||||||
export default class UserPurchaseHandler {
|
export default class UserPurchaseHandler {
|
||||||
constructor(bot) {
|
static async viewPurchasePage(userId, page) {
|
||||||
this.bot = bot;
|
|
||||||
this.userStates = new Map();
|
|
||||||
}
|
|
||||||
|
|
||||||
async viewPurchasePage(userId, page) {
|
|
||||||
try {
|
try {
|
||||||
const limit = 10;
|
const limit = 10;
|
||||||
const offset = (page || 0) * limit;
|
const offset = (page || 0) * limit;
|
||||||
@@ -17,22 +13,7 @@ export default class UserPurchaseHandler {
|
|||||||
const previousPage = page > 0 ? page - 1 : 0;
|
const previousPage = page > 0 ? page - 1 : 0;
|
||||||
const nextPage = page + 1;
|
const nextPage = page + 1;
|
||||||
|
|
||||||
const purchases = await db.allAsync(`
|
const purchases = await PurchaseService.getPurchasesByUserId(userId, limit, offset);
|
||||||
SELECT
|
|
||||||
p.*,
|
|
||||||
pr.name as product_name,
|
|
||||||
pr.description,
|
|
||||||
l.country,
|
|
||||||
l.city,
|
|
||||||
l.district
|
|
||||||
FROM purchases p
|
|
||||||
JOIN products pr ON p.product_id = pr.id
|
|
||||||
JOIN locations l ON pr.location_id = l.id
|
|
||||||
WHERE p.user_id = ?
|
|
||||||
ORDER BY p.purchase_date DESC
|
|
||||||
LIMIT ?
|
|
||||||
OFFSET ?
|
|
||||||
`, [userId, limit, offset]);
|
|
||||||
|
|
||||||
if ((purchases.length === 0) && (page == 0)) {
|
if ((purchases.length === 0) && (page == 0)) {
|
||||||
return {
|
return {
|
||||||
@@ -76,22 +57,22 @@ export default class UserPurchaseHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handlePurchaseListPage(callbackQuery) {
|
static async handlePurchaseListPage(callbackQuery) {
|
||||||
const telegramId = callbackQuery.from.id;
|
const telegramId = callbackQuery.from.id;
|
||||||
const chatId = callbackQuery.message.chat.id;
|
const chatId = callbackQuery.message.chat.id;
|
||||||
|
|
||||||
const page = callbackQuery.data.replace('list_purchases_', '');
|
const page = callbackQuery.data.replace('list_purchases_', '');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const user = await User.getById(telegramId);
|
const user = await UserService.getUserByTelegramId(telegramId);
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
await this.bot.sendMessage(chatId, 'User not found.');
|
await bot.sendMessage(chatId, 'User not found.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const {text, markup} = await this.viewPurchasePage(user.id, parseInt(page));
|
const {text, markup} = await this.viewPurchasePage(user.id, parseInt(page));
|
||||||
await this.bot.editMessageText(text, {
|
await bot.editMessageText(text, {
|
||||||
chat_id: chatId,
|
chat_id: chatId,
|
||||||
message_id: callbackQuery.message.message_id,
|
message_id: callbackQuery.message.message_id,
|
||||||
reply_markup: markup,
|
reply_markup: markup,
|
||||||
@@ -102,70 +83,52 @@ export default class UserPurchaseHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async showPurchases(msg) {
|
static async showPurchases(msg) {
|
||||||
// const userId = callbackQuery.from.id;
|
|
||||||
// const chatId = callbackQuery.message.chat.id;
|
|
||||||
|
|
||||||
const chatId = msg.chat.id;
|
const chatId = msg.chat.id;
|
||||||
const telegramId = msg.from.id;
|
const telegramId = msg.from.id;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
const user = await User.getById(telegramId);
|
const user = await UserService.getUserByTelegramId(telegramId);
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
await this.bot.sendMessage(chatId, 'User not found.');
|
await bot.sendMessage(chatId, 'User not found.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const {text, markup} = await this.viewPurchasePage(user.id, 0);
|
const {text, markup} = await this.viewPurchasePage(user.id, 0);
|
||||||
|
|
||||||
await this.bot.sendMessage(chatId, text, {reply_markup: markup, parse_mode: 'Markdown'});
|
await bot.sendMessage(chatId, text, {reply_markup: markup, parse_mode: 'Markdown'});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in handleSubcategorySelection:', error);
|
console.error('Error in handleSubcategorySelection:', error);
|
||||||
await this.bot.sendMessage(chatId, 'Error loading products. Please try again.');
|
await bot.sendMessage(chatId, 'Error loading products. Please try again.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async viewPurchase(callbackQuery) {
|
static async viewPurchase(callbackQuery) {
|
||||||
const chatId = callbackQuery.message.chat.id;
|
const chatId = callbackQuery.message.chat.id;
|
||||||
const purchaseId = callbackQuery.data.replace('view_purchase_', '');
|
const purchaseId = callbackQuery.data.replace('view_purchase_', '');
|
||||||
|
|
||||||
const purchase = await db.getAsync(`
|
const purchase = await PurchaseService.getPurchaseById(purchaseId);
|
||||||
SELECT
|
|
||||||
p.*,
|
|
||||||
pr.name as product_name,
|
|
||||||
pr.description,
|
|
||||||
l.country,
|
|
||||||
l.city,
|
|
||||||
l.district
|
|
||||||
FROM purchases p
|
|
||||||
JOIN products pr ON p.product_id = pr.id
|
|
||||||
JOIN locations l ON pr.location_id = l.id
|
|
||||||
WHERE p.id = ?
|
|
||||||
`, [purchaseId]);
|
|
||||||
|
|
||||||
if (!purchase) {
|
if (!purchase) {
|
||||||
await this.bot.sendMessage(chatId, "No such purchase");
|
await bot.sendMessage(chatId, "No such purchase");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const product = await db.getAsync(
|
const product = await ProductService.getProductById(purchase.product_id)
|
||||||
`SELECT * FROM products WHERE id = ?`,
|
|
||||||
[purchase.product_id]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!product) {
|
if (!product) {
|
||||||
await this.bot.sendMessage(chatId, "No such product");
|
await bot.sendMessage(chatId, "No such product");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let hiddenPhotoMessage;
|
let hiddenPhotoMessage;
|
||||||
if (product.hidden_photo_url) {
|
if (product.hidden_photo_url) {
|
||||||
try {
|
try {
|
||||||
hiddenPhotoMessage = await this.bot.sendPhoto(chatId, product.hidden_photo_url, {caption: 'Hidden photo'});
|
hiddenPhotoMessage = await bot.sendPhoto(chatId, product.hidden_photo_url, {caption: 'Hidden photo'});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
hiddenPhotoMessage = await this.bot.sendPhoto(chatId, "./corrupt-photo.jpg", {caption: 'Hidden photo'})
|
hiddenPhotoMessage = await bot.sendPhoto(chatId, "./corrupt-photo.jpg", {caption: 'Hidden photo'})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,7 +154,7 @@ Coordinates: ${product.hidden_coordinates}
|
|||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
await this.bot.sendMessage(chatId, message, {reply_markup: keyboard});
|
await bot.sendMessage(chatId, message, {reply_markup: keyboard});
|
||||||
await this.bot.deleteMessage(chatId, callbackQuery.message.message_id);
|
await bot.deleteMessage(chatId, callbackQuery.message.message_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,23 +1,19 @@
|
|||||||
import db from '../config/database.js';
|
import db from '../../config/database.js';
|
||||||
import User from '../models/User.js';
|
import WalletGenerator from '../../utils/walletGenerator.js';
|
||||||
import WalletGenerator from '../utils/walletGenerator.js';
|
import WalletService from '../../utils/walletService.js';
|
||||||
import WalletService from '../utils/walletService.js';
|
import UserService from "../../services/userService.js";
|
||||||
|
import bot from "../../context/bot.js";
|
||||||
|
|
||||||
export default class UserWalletsHandler {
|
export default class UserWalletsHandler {
|
||||||
constructor(bot) {
|
static async showBalance(msg) {
|
||||||
this.bot = bot;
|
|
||||||
this.userStates = new Map();
|
|
||||||
}
|
|
||||||
|
|
||||||
async showBalance(msg) {
|
|
||||||
const chatId = msg.chat.id;
|
const chatId = msg.chat.id;
|
||||||
const userId = msg.from.id;
|
const telegramId = msg.from.id;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const user = await db.getAsync('SELECT id FROM users WHERE telegram_id = ?', [userId.toString()]);
|
const user = await UserService.getUserByTelegramId(telegramId.toString());
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
await this.bot.sendMessage(chatId, 'Profile not found. Please use /start to create one.');
|
await bot.sendMessage(chatId, 'Profile not found. Please use /start to create one.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,16 +88,17 @@ export default class UserWalletsHandler {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.bot.sendMessage(chatId, message, {
|
await bot.sendMessage(chatId, message, {
|
||||||
reply_markup: keyboard,
|
reply_markup: keyboard,
|
||||||
parse_mode: 'Markdown'
|
parse_mode: 'Markdown'
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in showBalance:', error);
|
console.error('Error in showBalance:', error);
|
||||||
await this.bot.sendMessage(chatId, 'Error loading balance. Please try again.');
|
await bot.sendMessage(chatId, 'Error loading balance. Please try again.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async handleAddWallet(callbackQuery) {
|
|
||||||
|
static async handleAddWallet(callbackQuery) {
|
||||||
const chatId = callbackQuery.message.chat.id;
|
const chatId = callbackQuery.message.chat.id;
|
||||||
|
|
||||||
const cryptoOptions = [
|
const cryptoOptions = [
|
||||||
@@ -122,7 +119,7 @@ export default class UserWalletsHandler {
|
|||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
await this.bot.editMessageText(
|
await bot.editMessageText(
|
||||||
'🔐 Select cryptocurrency to generate wallet:',
|
'🔐 Select cryptocurrency to generate wallet:',
|
||||||
{
|
{
|
||||||
chat_id: chatId,
|
chat_id: chatId,
|
||||||
@@ -131,13 +128,15 @@ export default class UserWalletsHandler {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
async handleGenerateWallet(callbackQuery) {
|
|
||||||
|
static async handleGenerateWallet(callbackQuery) {
|
||||||
const chatId = callbackQuery.message.chat.id;
|
const chatId = callbackQuery.message.chat.id;
|
||||||
const userId = callbackQuery.from.id;
|
const telegramId = callbackQuery.from.id;
|
||||||
const walletType = callbackQuery.data.replace('generate_wallet_', '').replace('_', ' ');
|
const walletType = callbackQuery.data.replace('generate_wallet_', '').replace('_', ' ');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const user = await User.getById(userId);
|
const user = await UserService.getUserByTelegramId(telegramId);
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw new Error('User not found');
|
throw new Error('User not found');
|
||||||
}
|
}
|
||||||
@@ -148,7 +147,7 @@ export default class UserWalletsHandler {
|
|||||||
// Generate new wallets
|
// Generate new wallets
|
||||||
const mnemonic = await WalletGenerator.generateMnemonic();
|
const mnemonic = await WalletGenerator.generateMnemonic();
|
||||||
const wallets = await WalletGenerator.generateWallets(mnemonic);
|
const wallets = await WalletGenerator.generateWallets(mnemonic);
|
||||||
const encryptedMnemonic = await WalletGenerator.encryptMnemonic(mnemonic, userId);
|
const encryptedMnemonic = await WalletGenerator.encryptMnemonic(mnemonic, telegramId);
|
||||||
|
|
||||||
// Get the base wallet type (ETH for ERC-20, TRON for TRC-20)
|
// Get the base wallet type (ETH for ERC-20, TRON for TRC-20)
|
||||||
const baseType = this.getBaseWalletType(walletType);
|
const baseType = this.getBaseWalletType(walletType);
|
||||||
@@ -197,7 +196,7 @@ export default class UserWalletsHandler {
|
|||||||
|
|
||||||
message += `\n⚠️ Important: Your recovery phrase has been securely stored. Keep your wallet address safe!`;
|
message += `\n⚠️ Important: Your recovery phrase has been securely stored. Keep your wallet address safe!`;
|
||||||
|
|
||||||
await this.bot.editMessageText(message, {
|
await bot.editMessageText(message, {
|
||||||
chat_id: chatId,
|
chat_id: chatId,
|
||||||
message_id: callbackQuery.message.message_id,
|
message_id: callbackQuery.message.message_id,
|
||||||
parse_mode: 'Markdown',
|
parse_mode: 'Markdown',
|
||||||
@@ -215,7 +214,7 @@ export default class UserWalletsHandler {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error generating wallet:', error);
|
console.error('Error generating wallet:', error);
|
||||||
await this.bot.editMessageText(
|
await bot.editMessageText(
|
||||||
'❌ Error generating wallet. Please try again.',
|
'❌ Error generating wallet. Please try again.',
|
||||||
{
|
{
|
||||||
chat_id: chatId,
|
chat_id: chatId,
|
||||||
@@ -229,12 +228,13 @@ export default class UserWalletsHandler {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async handleTopUpWallet(callbackQuery) {
|
|
||||||
|
static async handleTopUpWallet(callbackQuery) {
|
||||||
const chatId = callbackQuery.message.chat.id;
|
const chatId = callbackQuery.message.chat.id;
|
||||||
const userId = callbackQuery.from.id;
|
const telegramId = callbackQuery.from.id;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const user = await db.getAsync('SELECT id FROM users WHERE telegram_id = ?', [userId.toString()]);
|
const user = await UserService.getUserByTelegramId(telegramId);
|
||||||
|
|
||||||
// Get crypto wallets
|
// Get crypto wallets
|
||||||
const cryptoWallets = await db.allAsync(`
|
const cryptoWallets = await db.allAsync(`
|
||||||
@@ -245,7 +245,7 @@ export default class UserWalletsHandler {
|
|||||||
`, [user.id]);
|
`, [user.id]);
|
||||||
|
|
||||||
if (cryptoWallets.length === 0) {
|
if (cryptoWallets.length === 0) {
|
||||||
await this.bot.editMessageText(
|
await bot.editMessageText(
|
||||||
'You don\'t have any wallets yet.',
|
'You don\'t have any wallets yet.',
|
||||||
{
|
{
|
||||||
chat_id: chatId,
|
chat_id: chatId,
|
||||||
@@ -295,7 +295,7 @@ export default class UserWalletsHandler {
|
|||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
await this.bot.editMessageText(message, {
|
await bot.editMessageText(message, {
|
||||||
chat_id: chatId,
|
chat_id: chatId,
|
||||||
message_id: callbackQuery.message.message_id,
|
message_id: callbackQuery.message.message_id,
|
||||||
parse_mode: 'Markdown',
|
parse_mode: 'Markdown',
|
||||||
@@ -303,15 +303,16 @@ export default class UserWalletsHandler {
|
|||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in handleTopUpWallet:', error);
|
console.error('Error in handleTopUpWallet:', error);
|
||||||
await this.bot.sendMessage(chatId, 'Error loading wallets. Please try again.');
|
await bot.sendMessage(chatId, 'Error loading wallets. Please try again.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async handleWalletHistory(callbackQuery) {
|
|
||||||
|
static async handleWalletHistory(callbackQuery) {
|
||||||
const chatId = callbackQuery.message.chat.id;
|
const chatId = callbackQuery.message.chat.id;
|
||||||
const userId = callbackQuery.from.id;
|
const telegramId = callbackQuery.from.id;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const user = await db.getAsync('SELECT id FROM users WHERE telegram_id = ?', [userId.toString()]);
|
const user = UserService.getUserByTelegramId(telegramId);
|
||||||
|
|
||||||
const transactions = await db.allAsync(`
|
const transactions = await db.allAsync(`
|
||||||
SELECT type, amount, tx_hash, created_at, wallet_type
|
SELECT type, amount, tx_hash, created_at, wallet_type
|
||||||
@@ -322,7 +323,7 @@ export default class UserWalletsHandler {
|
|||||||
`, [user.id]);
|
`, [user.id]);
|
||||||
|
|
||||||
if (transactions.length === 0) {
|
if (transactions.length === 0) {
|
||||||
await this.bot.editMessageText(
|
await bot.editMessageText(
|
||||||
'No transactions found.',
|
'No transactions found.',
|
||||||
{
|
{
|
||||||
chat_id: chatId,
|
chat_id: chatId,
|
||||||
@@ -346,7 +347,7 @@ export default class UserWalletsHandler {
|
|||||||
message += `🕒 ${date}\n\n`;
|
message += `🕒 ${date}\n\n`;
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.bot.editMessageText(message, {
|
await bot.editMessageText(message, {
|
||||||
chat_id: chatId,
|
chat_id: chatId,
|
||||||
message_id: callbackQuery.message.message_id,
|
message_id: callbackQuery.message.message_id,
|
||||||
parse_mode: 'Markdown',
|
parse_mode: 'Markdown',
|
||||||
@@ -358,15 +359,16 @@ export default class UserWalletsHandler {
|
|||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in handleWalletHistory:', error);
|
console.error('Error in handleWalletHistory:', error);
|
||||||
await this.bot.sendMessage(chatId, 'Error loading transaction history. Please try again.');
|
await bot.sendMessage(chatId, 'Error loading transaction history. Please try again.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async handleViewArchivedWallets(callbackQuery) {
|
|
||||||
|
static async handleViewArchivedWallets(callbackQuery) {
|
||||||
const chatId = callbackQuery.message.chat.id;
|
const chatId = callbackQuery.message.chat.id;
|
||||||
const userId = callbackQuery.from.id;
|
const telegramId = callbackQuery.from.id;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const user = await db.getAsync('SELECT id FROM users WHERE telegram_id = ?', [userId.toString()]);
|
const user = await UserService.getUserByTelegramId(telegramId.toString());
|
||||||
|
|
||||||
// Get archived wallets and validate timestamps
|
// Get archived wallets and validate timestamps
|
||||||
const archivedWallets = await db.allAsync(`
|
const archivedWallets = await db.allAsync(`
|
||||||
@@ -384,7 +386,7 @@ export default class UserWalletsHandler {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (validArchivedWallets.length === 0) {
|
if (validArchivedWallets.length === 0) {
|
||||||
await this.bot.editMessageText(
|
await bot.editMessageText(
|
||||||
'No archived wallets found.',
|
'No archived wallets found.',
|
||||||
{
|
{
|
||||||
chat_id: chatId,
|
chat_id: chatId,
|
||||||
@@ -482,7 +484,7 @@ export default class UserWalletsHandler {
|
|||||||
|
|
||||||
message += `💰 *Total Value of Archived Wallets:* $${totalUsdValue.toFixed(2)}`;
|
message += `💰 *Total Value of Archived Wallets:* $${totalUsdValue.toFixed(2)}`;
|
||||||
|
|
||||||
await this.bot.editMessageText(message, {
|
await bot.editMessageText(message, {
|
||||||
chat_id: chatId,
|
chat_id: chatId,
|
||||||
message_id: callbackQuery.message.message_id,
|
message_id: callbackQuery.message.message_id,
|
||||||
parse_mode: 'Markdown',
|
parse_mode: 'Markdown',
|
||||||
@@ -494,15 +496,16 @@ export default class UserWalletsHandler {
|
|||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in handleViewArchivedWallets:', error);
|
console.error('Error in handleViewArchivedWallets:', error);
|
||||||
await this.bot.sendMessage(chatId, 'Error loading archived wallets. Please try again.');
|
await bot.sendMessage(chatId, 'Error loading archived wallets. Please try again.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async handleRefreshBalance(callbackQuery) {
|
|
||||||
|
static async handleRefreshBalance(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;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.bot.editMessageText(
|
await bot.editMessageText(
|
||||||
'🔄 Refreshing balances...',
|
'🔄 Refreshing balances...',
|
||||||
{
|
{
|
||||||
chat_id: chatId,
|
chat_id: chatId,
|
||||||
@@ -517,10 +520,10 @@ export default class UserWalletsHandler {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Delete the "refreshing" message
|
// Delete the "refreshing" message
|
||||||
await this.bot.deleteMessage(chatId, messageId);
|
await bot.deleteMessage(chatId, messageId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in handleRefreshBalance:', error);
|
console.error('Error in handleRefreshBalance:', error);
|
||||||
await this.bot.editMessageText(
|
await bot.editMessageText(
|
||||||
'❌ Error refreshing balances. Please try again.',
|
'❌ Error refreshing balances. Please try again.',
|
||||||
{
|
{
|
||||||
chat_id: chatId,
|
chat_id: chatId,
|
||||||
@@ -534,21 +537,23 @@ export default class UserWalletsHandler {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async handleBackToBalance(callbackQuery) {
|
|
||||||
|
static async handleBackToBalance(callbackQuery) {
|
||||||
await this.showBalance({
|
await this.showBalance({
|
||||||
chat: { id: callbackQuery.message.chat.id },
|
chat: { id: callbackQuery.message.chat.id },
|
||||||
from: { id: callbackQuery.from.id }
|
from: { id: callbackQuery.from.id }
|
||||||
});
|
});
|
||||||
await this.bot.deleteMessage(callbackQuery.message.chat.id, callbackQuery.message.message_id);
|
await bot.deleteMessage(callbackQuery.message.chat.id, callbackQuery.message.message_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper methods
|
// Helper methods
|
||||||
getBaseWalletType(walletType) {
|
static getBaseWalletType(walletType) {
|
||||||
if (walletType.includes('TRC-20')) return 'TRON';
|
if (walletType.includes('TRC-20')) return 'TRON';
|
||||||
if (walletType.includes('ERC-20')) return 'ETH';
|
if (walletType.includes('ERC-20')) return 'ETH';
|
||||||
return walletType;
|
return walletType;
|
||||||
}
|
}
|
||||||
getWalletAddress(wallets, walletType) {
|
|
||||||
|
static getWalletAddress(wallets, walletType) {
|
||||||
if (walletType.includes('TRC-20')) return wallets.TRON.address;
|
if (walletType.includes('TRC-20')) return wallets.TRON.address;
|
||||||
if (walletType.includes('ERC-20')) return wallets.ETH.address;
|
if (walletType.includes('ERC-20')) return wallets.ETH.address;
|
||||||
if (walletType === 'BTC') return wallets.BTC.address;
|
if (walletType === 'BTC') return wallets.BTC.address;
|
||||||
@@ -556,7 +561,8 @@ export default class UserWalletsHandler {
|
|||||||
if (walletType === 'ETH') return wallets.ETH.address;
|
if (walletType === 'ETH') return wallets.ETH.address;
|
||||||
throw new Error('Invalid wallet type');
|
throw new Error('Invalid wallet type');
|
||||||
}
|
}
|
||||||
getNetworkName(walletType) {
|
|
||||||
|
static getNetworkName(walletType) {
|
||||||
if (walletType.includes('TRC-20')) return 'Tron Network (TRC-20)';
|
if (walletType.includes('TRC-20')) return 'Tron Network (TRC-20)';
|
||||||
if (walletType.includes('ERC-20')) return 'Ethereum Network (ERC-20)';
|
if (walletType.includes('ERC-20')) return 'Ethereum Network (ERC-20)';
|
||||||
if (walletType === 'BTC') return 'Bitcoin Network';
|
if (walletType === 'BTC') return 'Bitcoin Network';
|
||||||
@@ -1,160 +0,0 @@
|
|||||||
import db from '../config/database.js';
|
|
||||||
import Validators from '../utils/validators.js';
|
|
||||||
|
|
||||||
export default class UserLocationHandler {
|
|
||||||
constructor(bot) {
|
|
||||||
this.bot = bot;
|
|
||||||
}
|
|
||||||
|
|
||||||
async handleSetLocation(callbackQuery) {
|
|
||||||
const chatId = callbackQuery.message.chat.id;
|
|
||||||
const messageId = callbackQuery.message.message_id;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const countries = await db.allAsync('SELECT DISTINCT country FROM locations ORDER BY country');
|
|
||||||
|
|
||||||
if (countries.length === 0) {
|
|
||||||
await this.bot.editMessageText(
|
|
||||||
'No locations available yet.',
|
|
||||||
{
|
|
||||||
chat_id: chatId,
|
|
||||||
message_id: messageId,
|
|
||||||
reply_markup: {
|
|
||||||
inline_keyboard: [[
|
|
||||||
{ text: '« Back to Profile', callback_data: 'back_to_profile' }
|
|
||||||
]]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const keyboard = {
|
|
||||||
inline_keyboard: [
|
|
||||||
...countries.map(loc => [{
|
|
||||||
text: loc.country,
|
|
||||||
callback_data: `set_country_${loc.country}`
|
|
||||||
}]),
|
|
||||||
[{ text: '« Back to Profile', callback_data: 'back_to_profile' }]
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
await this.bot.editMessageText(
|
|
||||||
'🌍 Select your country:',
|
|
||||||
{
|
|
||||||
chat_id: chatId,
|
|
||||||
message_id: messageId,
|
|
||||||
reply_markup: keyboard
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error in handleSetLocation:', error);
|
|
||||||
await this.bot.sendMessage(chatId, 'Error loading countries. Please try again.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async handleSetCountry(callbackQuery) {
|
|
||||||
const chatId = callbackQuery.message.chat.id;
|
|
||||||
const messageId = callbackQuery.message.message_id;
|
|
||||||
const country = callbackQuery.data.replace('set_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: `set_city_${country}_${loc.city}`
|
|
||||||
}]),
|
|
||||||
[{ text: '« Back to Countries', callback_data: 'set_location' }]
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
await this.bot.editMessageText(
|
|
||||||
`🏙 Select city in ${country}:`,
|
|
||||||
{
|
|
||||||
chat_id: chatId,
|
|
||||||
message_id: messageId,
|
|
||||||
reply_markup: keyboard
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error in handleSetCountry:', error);
|
|
||||||
await this.bot.sendMessage(chatId, 'Error loading cities. Please try again.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async handleSetCity(callbackQuery) {
|
|
||||||
const chatId = callbackQuery.message.chat.id;
|
|
||||||
const messageId = callbackQuery.message.message_id;
|
|
||||||
const [country, city] = callbackQuery.data.replace('set_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: `set_district_${country}_${city}_${loc.district}`
|
|
||||||
}]),
|
|
||||||
[{ text: '« Back to Cities', callback_data: `set_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 handleSetCity:', error);
|
|
||||||
await this.bot.sendMessage(chatId, 'Error loading districts. Please try again.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async handleSetDistrict(callbackQuery) {
|
|
||||||
const chatId = callbackQuery.message.chat.id;
|
|
||||||
const messageId = callbackQuery.message.message_id;
|
|
||||||
const userId = callbackQuery.from.id;
|
|
||||||
const [country, city, district] = callbackQuery.data.replace('set_district_', '').split('_');
|
|
||||||
|
|
||||||
try {
|
|
||||||
await db.runAsync('BEGIN TRANSACTION');
|
|
||||||
|
|
||||||
await db.runAsync(
|
|
||||||
'UPDATE users SET country = ?, city = ?, district = ? WHERE telegram_id = ?',
|
|
||||||
[country, city, district, userId.toString()]
|
|
||||||
);
|
|
||||||
|
|
||||||
await db.runAsync('COMMIT');
|
|
||||||
|
|
||||||
await this.bot.editMessageText(
|
|
||||||
`✅ Location updated successfully!\n\nCountry: ${country}\nCity: ${city}\nDistrict: ${district}`,
|
|
||||||
{
|
|
||||||
chat_id: chatId,
|
|
||||||
message_id: messageId,
|
|
||||||
reply_markup: {
|
|
||||||
inline_keyboard: [[
|
|
||||||
{ text: '« Back to Profile', callback_data: 'back_to_profile' }
|
|
||||||
]]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
await db.runAsync('ROLLBACK');
|
|
||||||
console.error('Error in handleSetDistrict:', error);
|
|
||||||
await this.bot.sendMessage(chatId, 'Error updating location. Please try again.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
50
src/index.js
50
src/index.js
@@ -1,18 +1,16 @@
|
|||||||
import TelegramBot from 'node-telegram-bot-api';
|
import adminUserHandler from './handlers/adminHandlers/adminUserHandler.js';
|
||||||
import config from './config/config.js';
|
|
||||||
import UserHandler from './handlers/userHandler.js';
|
|
||||||
import UserProductHandler from './handlers/userProductHandler.js';
|
|
||||||
import UserWalletsHandler from './handlers/userWalletsHandler.js';
|
|
||||||
import UserLocationHandler from './handlers/userLocationHandler.js';
|
|
||||||
import AdminHandler from './handlers/adminHandler.js';
|
|
||||||
import AdminUserHandler from './handlers/adminUserHandler.js';
|
|
||||||
import AdminLocationHandler from './handlers/adminLocationHandler.js';
|
|
||||||
import AdminProductHandler from './handlers/adminProductHandler.js';
|
|
||||||
import ErrorHandler from './utils/errorHandler.js';
|
import ErrorHandler from './utils/errorHandler.js';
|
||||||
import User from './models/User.js';
|
import bot from "./context/bot.js";
|
||||||
import AdminUserLocationHandler from "./handlers/adminUserLocationHandler.js";
|
import userHandler from "./handlers/userHandlers/userHandler.js";
|
||||||
import AdminDumpHandler from "./handlers/adminDumpHandler.js";
|
import userPurchaseHandler from "./handlers/userHandlers/userPurchaseHandler.js";
|
||||||
import UserPurchaseHandler from "./handlers/userPurchaseHandler.js";
|
import userLocationHandler from "./handlers/userHandlers/userLocationHandler.js";
|
||||||
|
import userProductHandler from "./handlers/userHandlers/userProductHandler.js";
|
||||||
|
import userWalletsHandler from "./handlers/userHandlers/userWalletsHandler.js";
|
||||||
|
import adminHandler from "./handlers/adminHandlers/adminHandler.js";
|
||||||
|
import adminUserLocationHandler from "./handlers/adminHandlers/adminUserLocationHandler.js";
|
||||||
|
import adminDumpHandler from "./handlers/adminHandlers/adminDumpHandler.js";
|
||||||
|
import adminLocationHandler from "./handlers/adminHandlers/adminLocationHandler.js";
|
||||||
|
import adminProductHandler from "./handlers/adminHandlers/adminProductHandler.js";
|
||||||
|
|
||||||
// Debug logging function
|
// Debug logging function
|
||||||
const logDebug = (action, functionName) => {
|
const logDebug = (action, functionName) => {
|
||||||
@@ -20,30 +18,6 @@ const logDebug = (action, functionName) => {
|
|||||||
console.log(`[DEBUG] Calling Function: ${functionName}`);
|
console.log(`[DEBUG] Calling Function: ${functionName}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const initBot = () => {
|
|
||||||
try {
|
|
||||||
const bot = new TelegramBot(config.BOT_TOKEN, {polling: true});
|
|
||||||
console.log('Bot initialized successfully');
|
|
||||||
return bot;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to initialize bot:', error);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const bot = initBot();
|
|
||||||
const userHandler = new UserHandler(bot);
|
|
||||||
const userProductHandler = new UserProductHandler(bot);
|
|
||||||
const userWalletsHandler = new UserWalletsHandler(bot);
|
|
||||||
const userLocationHandler = new UserLocationHandler(bot);
|
|
||||||
const adminHandler = new AdminHandler(bot);
|
|
||||||
const adminUserHandler = new AdminUserHandler(bot);
|
|
||||||
const adminLocationHandler = new AdminLocationHandler(bot);
|
|
||||||
const adminUserLocationHandler = new AdminUserLocationHandler(bot);
|
|
||||||
const adminProductHandler = new AdminProductHandler(bot);
|
|
||||||
const adminDumpHandler = new AdminDumpHandler(bot);
|
|
||||||
const userPurchaseHandler = new UserPurchaseHandler(bot);
|
|
||||||
|
|
||||||
// Start command - Create user profile
|
// Start command - Create user profile
|
||||||
bot.onText(/\/start/, async (msg) => {
|
bot.onText(/\/start/, async (msg) => {
|
||||||
logDebug('/start', 'handleStart');
|
logDebug('/start', 'handleStart');
|
||||||
|
|||||||
@@ -1,127 +0,0 @@
|
|||||||
import db from '../config/database.js';
|
|
||||||
import Wallet from "./Wallet.js";
|
|
||||||
|
|
||||||
export default class User {
|
|
||||||
static async create(telegramId, username) {
|
|
||||||
try {
|
|
||||||
// First check if user exists
|
|
||||||
const existingUser = await this.getById(telegramId);
|
|
||||||
if (existingUser) {
|
|
||||||
return existingUser.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Begin transaction
|
|
||||||
await db.runAsync('BEGIN TRANSACTION');
|
|
||||||
|
|
||||||
// Create new user
|
|
||||||
const result = await db.runAsync(
|
|
||||||
'INSERT INTO users (telegram_id, username) VALUES (?, ?)',
|
|
||||||
[telegramId.toString(), username]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Commit transaction
|
|
||||||
await db.runAsync('COMMIT');
|
|
||||||
|
|
||||||
return result.lastID;
|
|
||||||
} catch (error) {
|
|
||||||
// Rollback on error
|
|
||||||
await db.runAsync('ROLLBACK');
|
|
||||||
console.error('Error creating user:', error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static async getById(telegramId) {
|
|
||||||
try {
|
|
||||||
return await db.getAsync(
|
|
||||||
'SELECT * FROM users WHERE telegram_id = ?',
|
|
||||||
[telegramId.toString()]
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error getting user:', error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static async getUserStats(telegramId) {
|
|
||||||
try {
|
|
||||||
return await db.getAsync(`
|
|
||||||
SELECT
|
|
||||||
u.*,
|
|
||||||
COUNT(DISTINCT p.id) as purchase_count,
|
|
||||||
COALESCE(SUM(p.total_price), 0) as total_spent,
|
|
||||||
COUNT(DISTINCT cw.id) as crypto_wallet_count,
|
|
||||||
COUNT(DISTINCT cw2.id) as archived_wallet_count
|
|
||||||
FROM users u
|
|
||||||
LEFT JOIN purchases p ON u.id = p.user_id
|
|
||||||
LEFT JOIN crypto_wallets cw ON u.id = cw.user_id AND cw.wallet_type NOT LIKE '%_%'
|
|
||||||
LEFT JOIN crypto_wallets cw2 ON u.id = cw2.user_id AND cw2.wallet_type LIKE '%_%'
|
|
||||||
WHERE u.telegram_id = ?
|
|
||||||
GROUP BY u.id
|
|
||||||
`, [telegramId.toString()]);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error getting user stats:', error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static async updateUserStatus(telegramId, status) {
|
|
||||||
// statuses
|
|
||||||
// 0 - active
|
|
||||||
// 1 - deleted
|
|
||||||
// 2 - blocked
|
|
||||||
|
|
||||||
try {
|
|
||||||
await db.runAsync('BEGIN TRANSACTION');
|
|
||||||
|
|
||||||
// Update user status
|
|
||||||
await db.runAsync('UPDATE users SET status = ? WHERE telegram_id = ?', [status, telegramId.toString()]);
|
|
||||||
|
|
||||||
// Commit transaction
|
|
||||||
await db.runAsync('COMMIT');
|
|
||||||
} catch (e) {
|
|
||||||
await db.runAsync("ROLLBACK");
|
|
||||||
console.error('Error deleting user:', error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static async delete(telegramId) {
|
|
||||||
try {
|
|
||||||
await db.runAsync('BEGIN TRANSACTION');
|
|
||||||
|
|
||||||
// Delete user and his data
|
|
||||||
await db.runAsync('DELETE FROM users WHERE telegram_id = ?', [telegramId.toString()]);
|
|
||||||
await db.runAsync('DELETE FROM transactions WHERE user_id = ?', [telegramId.toString()]);
|
|
||||||
await db.runAsync('DELETE FROM purchases WHERE user_id = ?', [telegramId.toString()]);
|
|
||||||
await db.runAsync('DELETE FROM crypto_wallets WHERE user_id = ?', [telegramId.toString()]);
|
|
||||||
|
|
||||||
// Commit transaction
|
|
||||||
await db.runAsync('COMMIT');
|
|
||||||
} catch (e) {
|
|
||||||
await db.runAsync("ROLLBACK");
|
|
||||||
console.error('Error deleting user:', error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static async recalculateBalance(telegramId) {
|
|
||||||
const user = await User.getById(telegramId);
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const archivedBalance = await Wallet.getArchivedWalletsBalance(user.id);
|
|
||||||
const activeBalance = await Wallet.getActiveWalletsBalance(user.id);
|
|
||||||
|
|
||||||
const purchases = await db.getAsync(
|
|
||||||
`SELECT SUM(total_price) as total_sum FROM purchases WHERE user_id = ?`,
|
|
||||||
[user.id]
|
|
||||||
);
|
|
||||||
|
|
||||||
const userTotalBalance = (activeBalance + archivedBalance) - (purchases?.total_sum || 0);
|
|
||||||
|
|
||||||
await db.runAsync(`UPDATE users SET total_balance = ? WHERE id = ?`, [userTotalBalance, user.id]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
27
src/services/categoryService.js
Normal file
27
src/services/categoryService.js
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import db from "../config/database.js";
|
||||||
|
|
||||||
|
class CategoryService {
|
||||||
|
static async getCategoriesByLocationId(locationId) {
|
||||||
|
return await db.allAsync(
|
||||||
|
'SELECT id, name FROM categories WHERE location_id = ? ORDER BY name',
|
||||||
|
[locationId]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static async getSubcategoriesByCategoryId(categoryId) {
|
||||||
|
return await db.allAsync(
|
||||||
|
'SELECT id, name FROM subcategories WHERE category_id = ? ORDER BY name',
|
||||||
|
[categoryId]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static async getCategoryById(categoryId) {
|
||||||
|
return await db.getAsync('SELECT id, name FROM categories WHERE id = ?', [categoryId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static async getSubcategoryById(subcategoryId) {
|
||||||
|
return await db.getAsync('SELECT id, name FROM subcategories WHERE id = ?', [subcategoryId]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CategoryService;
|
||||||
37
src/services/locationService.js
Normal file
37
src/services/locationService.js
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import db from "../config/database.js";
|
||||||
|
|
||||||
|
class LocationService {
|
||||||
|
static async getCountries() {
|
||||||
|
return await db.allAsync('SELECT DISTINCT country FROM locations ORDER BY country');
|
||||||
|
}
|
||||||
|
|
||||||
|
static async getCitiesByCountry(country) {
|
||||||
|
return await db.allAsync(
|
||||||
|
'SELECT DISTINCT city FROM locations WHERE country = ? ORDER BY city',
|
||||||
|
[country]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static async getDistrictsByCountryAndCity(country, city) {
|
||||||
|
return await db.allAsync(
|
||||||
|
'SELECT district FROM locations WHERE country = ? AND city = ? ORDER BY district',
|
||||||
|
[country, city]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static async getLocation(country, city, district) {
|
||||||
|
return await db.getAsync(
|
||||||
|
'SELECT id FROM locations WHERE country = ? AND city = ? AND district = ?',
|
||||||
|
[country, city, district]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static async getLocationById(locationId) {
|
||||||
|
return await db.getAsync(
|
||||||
|
'SELECT country, city, district FROM locations WHERE id = ?',
|
||||||
|
[locationId]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LocationService;
|
||||||
37
src/services/productService.js
Normal file
37
src/services/productService.js
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import db from "../config/database.js";
|
||||||
|
|
||||||
|
class ProductService {
|
||||||
|
static async getProductById(productId) {
|
||||||
|
try {
|
||||||
|
return await db.getAsync(`SELECT * FROM products WHERE id = ?`, [productId]);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error get product:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async getDetailedProductById(productId) {
|
||||||
|
return await db.getAsync(
|
||||||
|
`SELECT p.*, c.name as category_name, s.name as subcategory_name
|
||||||
|
FROM products p
|
||||||
|
JOIN categories c ON p.category_id = c.id
|
||||||
|
JOIN subcategories s ON p.subcategory_id = s.id
|
||||||
|
WHERE p.id = ?`,
|
||||||
|
[productId]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static async getProductsByLocationAndCategory(locationId, categoryId, subcategoryId) {
|
||||||
|
return await db.allAsync(
|
||||||
|
`SELECT id, name, price, description, quantity_in_stock, photo_url
|
||||||
|
FROM products
|
||||||
|
WHERE location_id = ? AND category_id = ? AND subcategory_id = ?
|
||||||
|
AND quantity_in_stock > 0
|
||||||
|
ORDER BY name`,
|
||||||
|
[locationId, categoryId, subcategoryId]
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ProductService
|
||||||
59
src/services/purchaseService.js
Normal file
59
src/services/purchaseService.js
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import db from "../config/database.js";
|
||||||
|
|
||||||
|
class PurchaseService {
|
||||||
|
static async getPurchasesByUserId(userId, limit, offset) {
|
||||||
|
try {
|
||||||
|
return await db.allAsync(`
|
||||||
|
SELECT
|
||||||
|
p.*,
|
||||||
|
pr.name as product_name,
|
||||||
|
pr.description,
|
||||||
|
l.country,
|
||||||
|
l.city,
|
||||||
|
l.district
|
||||||
|
FROM purchases p
|
||||||
|
JOIN products pr ON p.product_id = pr.id
|
||||||
|
JOIN locations l ON pr.location_id = l.id
|
||||||
|
WHERE p.user_id = ?
|
||||||
|
ORDER BY p.purchase_date DESC
|
||||||
|
LIMIT ?
|
||||||
|
OFFSET ?
|
||||||
|
`, [userId, limit, offset]);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error get purchases:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async getPurchaseById(purchaseId) {
|
||||||
|
try {
|
||||||
|
return await db.getAsync(`
|
||||||
|
SELECT
|
||||||
|
p.*,
|
||||||
|
pr.name as product_name,
|
||||||
|
pr.description,
|
||||||
|
l.country,
|
||||||
|
l.city,
|
||||||
|
l.district
|
||||||
|
FROM purchases p
|
||||||
|
JOIN products pr ON p.product_id = pr.id
|
||||||
|
JOIN locations l ON pr.location_id = l.id
|
||||||
|
WHERE p.id = ?
|
||||||
|
`, [purchaseId]);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error get purchase:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static async createPurchase(userId, productId, walletType, quantity, totalPrice) {
|
||||||
|
await db.runAsync(
|
||||||
|
'INSERT INTO purchases (user_id, product_id, wallet_type, tx_hash, quantity, total_price) VALUES (?, ?, ?, ?, ?, ?)',
|
||||||
|
[userId, productId, walletType, "null", quantity, totalPrice]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PurchaseService
|
||||||
140
src/services/userService.js
Normal file
140
src/services/userService.js
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
import db from "../config/database.js";
|
||||||
|
import Wallet from "../models/Wallet.js";
|
||||||
|
|
||||||
|
class UserService {
|
||||||
|
static async getUserByUserId(userId) {
|
||||||
|
try {
|
||||||
|
return await db.getAsync(
|
||||||
|
'SELECT * FROM users WHERE id = ?',
|
||||||
|
[String(userId)]
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error getting user:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async getUserByTelegramId(telegramId) {
|
||||||
|
try {
|
||||||
|
return await db.getAsync(
|
||||||
|
'SELECT * FROM users WHERE telegram_id = ?',
|
||||||
|
[String(telegramId)]
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error getting user:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async getDetailedUserByTelegramId(telegramId) {
|
||||||
|
try {
|
||||||
|
return await db.getAsync(`
|
||||||
|
SELECT
|
||||||
|
u.*,
|
||||||
|
COUNT(DISTINCT p.id) as purchase_count,
|
||||||
|
COALESCE(SUM(p.total_price), 0) as total_spent,
|
||||||
|
COUNT(DISTINCT cw.id) as crypto_wallet_count,
|
||||||
|
COUNT(DISTINCT cw2.id) as archived_wallet_count
|
||||||
|
FROM users u
|
||||||
|
LEFT JOIN purchases p ON u.id = p.user_id
|
||||||
|
LEFT JOIN crypto_wallets cw ON u.id = cw.user_id AND cw.wallet_type NOT LIKE '%_%'
|
||||||
|
LEFT JOIN crypto_wallets cw2 ON u.id = cw2.user_id AND cw2.wallet_type LIKE '%_%'
|
||||||
|
WHERE u.telegram_id = ?
|
||||||
|
GROUP BY u.id
|
||||||
|
`, [telegramId.toString()]);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error getting user stats:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async createUser(userData) {
|
||||||
|
try {
|
||||||
|
const existingUser = await this.getUserByTelegramId(userData?.telegram_id);
|
||||||
|
|
||||||
|
if (existingUser) {
|
||||||
|
return existingUser.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fields = Object.keys(userData);
|
||||||
|
const values = [];
|
||||||
|
|
||||||
|
for (const field of fields) {
|
||||||
|
values.push(userData[field]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const marks = [];
|
||||||
|
for (let i = 0; i < fields.length; i++) {
|
||||||
|
marks.push("?");
|
||||||
|
}
|
||||||
|
|
||||||
|
const query = [
|
||||||
|
`INSERT INTO users (${fields.join(', ')})`,
|
||||||
|
`VALUES (${marks.join(', ')})`
|
||||||
|
].join("");
|
||||||
|
|
||||||
|
await db.runAsync('BEGIN TRANSACTION');
|
||||||
|
const result = await db.runAsync(query, [values]);
|
||||||
|
await db.runAsync('COMMIT');
|
||||||
|
return result.lastID;
|
||||||
|
} catch (error) {
|
||||||
|
await db.runAsync('ROLLBACK');
|
||||||
|
console.error('Error creating user:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async updateUser(userId, newUserData) {}
|
||||||
|
|
||||||
|
static async deleteUser() {}
|
||||||
|
|
||||||
|
static async recalculateUserBalanceByTelegramId(telegramId) {
|
||||||
|
const user = await this.getUserByTelegramId(telegramId);
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const archivedBalance = await Wallet.getArchivedWalletsBalance(user.id);
|
||||||
|
const activeBalance = await Wallet.getActiveWalletsBalance(user.id);
|
||||||
|
|
||||||
|
const purchases = await db.getAsync(
|
||||||
|
`SELECT SUM(total_price) as total_sum FROM purchases WHERE user_id = ?`,
|
||||||
|
[user.id]
|
||||||
|
);
|
||||||
|
|
||||||
|
const userTotalBalance = (activeBalance + archivedBalance) - (purchases?.total_sum || 0);
|
||||||
|
|
||||||
|
await db.runAsync(`UPDATE users SET total_balance = ? WHERE id = ?`, [userTotalBalance, user.id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static async updateUserLocation(telegramId, country, city, district) {
|
||||||
|
await db.runAsync(
|
||||||
|
'UPDATE users SET country = ?, city = ?, district = ? WHERE telegram_id = ?',
|
||||||
|
[country, city, district, telegramId.toString()]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static async updateUserStatus(telegramId, status) {
|
||||||
|
// statuses
|
||||||
|
// 0 - active
|
||||||
|
// 1 - deleted
|
||||||
|
// 2 - blocked
|
||||||
|
|
||||||
|
try {
|
||||||
|
await db.runAsync('BEGIN TRANSACTION');
|
||||||
|
|
||||||
|
// Update user status
|
||||||
|
await db.runAsync('UPDATE users SET status = ? WHERE telegram_id = ?', [status, telegramId.toString()]);
|
||||||
|
|
||||||
|
// Commit transaction
|
||||||
|
await db.runAsync('COMMIT');
|
||||||
|
} catch (e) {
|
||||||
|
await db.runAsync("ROLLBACK");
|
||||||
|
console.error('Error deleting user:', e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default UserService;
|
||||||
3
src/services/walletService.js
Normal file
3
src/services/walletService.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
class WalletService {
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user