refactor(arch): Phase 2 — deduplicate isAdmin, convertToUsd, getBaseWalletType

- #54: Extract isAdmin() to src/middleware/auth.js, remove duplicates from 7 admin handlers
- #55: Add WalletUtils.convertToUsd(), replace 8 switch-case blocks across 4 files
- #56: Unify getBaseWalletType() — keep only WalletUtils version (most complete),
  remove duplicates from Wallet.js and userWalletsHandler.js

New file: src/middleware/auth.js
Net: -215 lines, +80 lines

Closes: #54, #55, #56
This commit is contained in:
NW
2026-06-17 22:10:34 +01:00
parent de415633be
commit 68d83807ad
13 changed files with 80 additions and 215 deletions

View File

@@ -1,4 +1,4 @@
import config from '../../config/config.js';
import { isAdmin } from '../../middleware/auth.js';
import fs from "fs";
import db from "../../config/database.js";
import archiver from "archiver";
@@ -7,14 +7,11 @@ import bot from "../../context/bot.js";
import userStates from "../../context/userStates.js";
export default class AdminDumpHandler {
static isAdmin(userId) {
return config.ADMIN_IDS.includes(userId.toString());
}
static async handleDump(msg) {
const chatId = msg.chat.id;
if (!this.isAdmin(msg.from.id)) {
if (!isAdmin(msg.from.id)) {
await bot.sendMessage(chatId, 'Unauthorized access.');
return;
}
@@ -33,7 +30,7 @@ export default class AdminDumpHandler {
}
static async handleExportDatabase(callbackQuery) {
if (!this.isAdmin(callbackQuery.from.id)) {
if (!isAdmin(callbackQuery.from.id)) {
return;
}
@@ -81,7 +78,7 @@ export default class AdminDumpHandler {
}
static async handleImportDatabase(callbackQuery) {
if (!this.isAdmin(callbackQuery.from.id)) {
if (!isAdmin(callbackQuery.from.id)) {
return;
}
@@ -130,7 +127,7 @@ export default class AdminDumpHandler {
return false;
}
if (!this.isAdmin(msg.from.id)) {
if (!isAdmin(msg.from.id)) {
await bot.sendMessage(chatId, 'Unauthorized access.');
return;
}
@@ -156,7 +153,7 @@ export default class AdminDumpHandler {
}
static async confirmImport(callbackQuery) {
if (!this.isAdmin(callbackQuery.from.id)) {
if (!isAdmin(callbackQuery.from.id)) {
return;
}
}

View File

@@ -1,15 +1,11 @@
import config from '../../config/config.js';
import { isAdmin } from '../../middleware/auth.js';
import bot from "../../context/bot.js";
export default class AdminHandler {
static isAdmin(userId) {
return config.ADMIN_IDS.includes(userId.toString());
}
static async handleAdminCommand(msg) {
const chatId = msg.chat.id;
if (!this.isAdmin(msg.from.id)) {
if (!isAdmin(msg.from.id)) {
await bot.sendMessage(chatId, 'Unauthorized access.');
return;
}

View File

@@ -1,16 +1,13 @@
import db from '../../config/database.js';
import Validators from '../../utils/validators.js';
import config from '../../config/config.js';
import { isAdmin } from '../../middleware/auth.js';
import userStates from "../../context/userStates.js";
import bot from "../../context/bot.js";
export default class AdminLocationHandler {
static isAdmin(userId) {
return config.ADMIN_IDS.includes(userId.toString());
}
static async handleAddLocation(callbackQuery) {
if (!this.isAdmin(callbackQuery.from.id)) {
if (!isAdmin(callbackQuery.from.id)) {
return;
}
@@ -38,7 +35,7 @@ export default class AdminLocationHandler {
return false;
}
if (!this.isAdmin(msg.from.id)) {
if (!isAdmin(msg.from.id)) {
await bot.sendMessage(chatId, 'Unauthorized access.');
return;
}
@@ -126,7 +123,7 @@ export default class AdminLocationHandler {
static async handleViewIP(callbackQuery) {
// Проверка прав администратора
if (!this.isAdmin(callbackQuery.from.id)) {
if (!isAdmin(callbackQuery.from.id)) {
return;
}
@@ -163,7 +160,7 @@ export default class AdminLocationHandler {
const chatId = msg.chat?.id || msg.message?.chat.id;
const messageId = msg.message?.message_id;
if (!this.isAdmin(msg.from?.id || msg.message?.from.id)) {
if (!isAdmin(msg.from?.id || msg.message?.from.id)) {
await bot.sendMessage(chatId, 'Unauthorized access.');
return;
}
@@ -241,7 +238,7 @@ export default class AdminLocationHandler {
}
static async handleDeleteLocation(callbackQuery) {
if (!this.isAdmin(callbackQuery.from.id)) {
if (!isAdmin(callbackQuery.from.id)) {
return;
}
@@ -284,7 +281,7 @@ export default class AdminLocationHandler {
}
static async handleConfirmDelete(callbackQuery) {
if (!this.isAdmin(callbackQuery.from.id)) {
if (!isAdmin(callbackQuery.from.id)) {
return;
}
@@ -337,7 +334,7 @@ export default class AdminLocationHandler {
}
static async backToMenu(callbackQuery) {
if (!this.isAdmin(callbackQuery.from.id)) {
if (!isAdmin(callbackQuery.from.id)) {
return;
}

View File

@@ -1,5 +1,5 @@
import db from '../../config/database.js';
import config from '../../config/config.js';
import { isAdmin } from '../../middleware/auth.js';
import fs from 'fs/promises';
import LocationService from "../../services/locationService.js";
import bot from "../../context/bot.js";
@@ -9,14 +9,11 @@ import ProductService from "../../services/productService.js";
import Validators from '../../utils/validators.js';
export default class AdminProductHandler {
static isAdmin(userId) {
return config.ADMIN_IDS.includes(userId.toString());
}
static async handleProductManagement(msg) {
const chatId = msg.chat?.id || msg.message?.chat.id;
if (!this.isAdmin(msg.from?.id || msg.message?.from.id)) {
if (!isAdmin(msg.from?.id || msg.message?.from.id)) {
await bot.sendMessage(chatId, 'Unauthorized access.');
return;
}
@@ -58,7 +55,7 @@ export default class AdminProductHandler {
}
static async handleCountrySelection(callbackQuery) {
if (!this.isAdmin(callbackQuery.from.id)) {
if (!isAdmin(callbackQuery.from.id)) {
return;
}
@@ -94,7 +91,7 @@ export default class AdminProductHandler {
}
static async handleCitySelection(callbackQuery) {
if (!this.isAdmin(callbackQuery.from.id)) {
if (!isAdmin(callbackQuery.from.id)) {
return;
}
@@ -130,7 +127,7 @@ export default class AdminProductHandler {
}
static async handleDistrictSelection(callbackQuery) {
if (!this.isAdmin(callbackQuery.from.id)) {
if (!isAdmin(callbackQuery.from.id)) {
return;
}
@@ -182,7 +179,7 @@ export default class AdminProductHandler {
return false;
}
if (!this.isAdmin(msg.from.id)) {
if (!isAdmin(msg.from.id)) {
await bot.sendMessage(chatId, 'Unauthorized access.');
return;
}
@@ -231,7 +228,7 @@ export default class AdminProductHandler {
}
static async handleAddCategory(callbackQuery) {
if (!this.isAdmin(callbackQuery.from.id)) {
if (!isAdmin(callbackQuery.from.id)) {
return;
}
@@ -265,7 +262,7 @@ export default class AdminProductHandler {
return false;
}
if (!this.isAdmin(msg.from.id)) {
if (!isAdmin(msg.from.id)) {
await bot.sendMessage(chatId, 'Неавторизованный доступ.');
return;
}
@@ -312,7 +309,7 @@ export default class AdminProductHandler {
}
// Редактирование категории
static async handleEditCategory(callbackQuery) {
if (!this.isAdmin(callbackQuery.from.id)) {
if (!isAdmin(callbackQuery.from.id)) {
return;
}
@@ -335,7 +332,7 @@ export default class AdminProductHandler {
);
}
static async handleCategorySelection(callbackQuery) {
if (!this.isAdmin(callbackQuery.from.id)) {
if (!isAdmin(callbackQuery.from.id)) {
return;
}
@@ -383,7 +380,7 @@ export default class AdminProductHandler {
return false;
}
if (!this.isAdmin(msg.from.id)) {
if (!isAdmin(msg.from.id)) {
await bot.sendMessage(chatId, 'Unauthorized access.');
return;
}
@@ -430,7 +427,7 @@ export default class AdminProductHandler {
}
static async handleAddSubcategory(callbackQuery) {
if (!this.isAdmin(callbackQuery.from.id)) {
if (!isAdmin(callbackQuery.from.id)) {
return;
}
@@ -532,7 +529,7 @@ export default class AdminProductHandler {
}
static async handleCategorySelection(callbackQuery) {
if (!this.isAdmin(callbackQuery.from.id)) {
if (!isAdmin(callbackQuery.from.id)) {
return;
}
@@ -573,7 +570,7 @@ export default class AdminProductHandler {
}
static async handleProductListPage(callbackQuery) {
if (!this.isAdmin(callbackQuery.from.id)) {
if (!isAdmin(callbackQuery.from.id)) {
return;
}
@@ -595,7 +592,7 @@ export default class AdminProductHandler {
}
static async handleAddProduct(callbackQuery) {
if (!this.isAdmin(callbackQuery.from.id)) {
if (!isAdmin(callbackQuery.from.id)) {
return;
}
@@ -654,7 +651,7 @@ export default class AdminProductHandler {
return false;
}
if (!this.isAdmin(msg.from.id)) {
if (!isAdmin(msg.from.id)) {
await bot.sendMessage(chatId, 'Unauthorized access.');
return;
}
@@ -762,7 +759,7 @@ export default class AdminProductHandler {
return false;
}
if (!this.isAdmin(msg.from.id)) {
if (!isAdmin(msg.from.id)) {
await bot.sendMessage(chatId, 'Unauthorized access.');
return;
}
@@ -863,7 +860,7 @@ export default class AdminProductHandler {
}
static async handleViewProduct(callbackQuery) {
if (!this.isAdmin(callbackQuery.from.id)) {
if (!isAdmin(callbackQuery.from.id)) {
return;
}
@@ -945,7 +942,7 @@ export default class AdminProductHandler {
}
static async handleProductEdit(callbackQuery) {
if (!this.isAdmin(callbackQuery.from.id)) {
if (!isAdmin(callbackQuery.from.id)) {
return;
}
@@ -1005,7 +1002,7 @@ export default class AdminProductHandler {
}
static async handleProductDelete(callbackQuery) {
if (!this.isAdmin(callbackQuery.from.id)) {
if (!isAdmin(callbackQuery.from.id)) {
return;
}
@@ -1047,7 +1044,7 @@ export default class AdminProductHandler {
}
static async handleConfirmDelete(callbackQuery) {
if (!this.isAdmin(callbackQuery.from.id)) {
if (!isAdmin(callbackQuery.from.id)) {
return;
}

View File

@@ -1,6 +1,6 @@
// adminUserHandler.js
import config from '../../config/config.js';
import { isAdmin } from '../../middleware/auth.js';
import db from '../../config/database.js';
import bot from "../../context/bot.js";
import UserService from "../../services/userService.js";
@@ -10,9 +10,6 @@ import userStates from "../../context/userStates.js";
import Validators from '../../utils/validators.js';
export default class AdminUserHandler {
static isAdmin(userId) {
return config.ADMIN_IDS.includes(userId.toString());
}
static async calculateStatistics() {
try {
@@ -156,7 +153,7 @@ export default class AdminUserHandler {
}
static async handleUserList(msg) {
if (!this.isAdmin(msg.from.id)) {
if (!isAdmin(msg.from.id)) {
await bot.sendMessage(msg.chat.id, 'Unauthorized access.');
return;
}
@@ -166,7 +163,7 @@ export default class AdminUserHandler {
}
static async handleUserListPage(callbackQuery) {
if (!this.isAdmin(callbackQuery.from.id)) {
if (!isAdmin(callbackQuery.from.id)) {
return;
}
@@ -187,7 +184,7 @@ export default class AdminUserHandler {
}
static async handleViewUser(callbackQuery) {
if (!this.isAdmin(callbackQuery.from.id)) return;
if (!isAdmin(callbackQuery.from.id)) return;
const telegramId = callbackQuery.data.replace('view_user_', '');
const chatId = callbackQuery.message.chat.id;
@@ -288,7 +285,7 @@ export default class AdminUserHandler {
}
static async handleDeleteUser(callbackQuery) {
if (!this.isAdmin(callbackQuery.from.id)) {
if (!isAdmin(callbackQuery.from.id)) {
return;
}
@@ -321,7 +318,7 @@ export default class AdminUserHandler {
}
static async handleConfirmDelete(callbackQuery) {
if (!this.isAdmin(callbackQuery.from.id)) {
if (!isAdmin(callbackQuery.from.id)) {
return;
}
@@ -358,7 +355,7 @@ export default class AdminUserHandler {
}
static async handleBlockUser(callbackQuery) {
if (!this.isAdmin(callbackQuery.from.id)) {
if (!isAdmin(callbackQuery.from.id)) {
return;
}
@@ -391,7 +388,7 @@ export default class AdminUserHandler {
}
static async handleConfirmBlock(callbackQuery) {
if (!this.isAdmin(callbackQuery.from.id)) {
if (!isAdmin(callbackQuery.from.id)) {
return;
}
@@ -428,7 +425,7 @@ export default class AdminUserHandler {
}
static async handleEditUserBalance(callbackQuery) {
if (!this.isAdmin(callbackQuery.from.id)) {
if (!isAdmin(callbackQuery.from.id)) {
return;
}
@@ -459,7 +456,7 @@ export default class AdminUserHandler {
}
static async handleBonusBalanceInput(msg) {
if (!this.isAdmin(msg.from.id)) {
if (!isAdmin(msg.from.id)) {
return;
}

View File

@@ -1,16 +1,13 @@
import db from '../../config/database.js';
import config from "../../config/config.js";
import { isAdmin } from '../../middleware/auth.js';
import LocationService from "../../services/locationService.js";
import bot from "../../context/bot.js";
import UserService from "../../services/userService.js";
export default class AdminUserLocationHandler {
static isAdmin(userId) {
return config.ADMIN_IDS.includes(userId.toString());
}
static async handleEditUserLocation(callbackQuery) {
if (!this.isAdmin(callbackQuery.from.id)) {
if (!isAdmin(callbackQuery.from.id)) {
return;
}
@@ -62,7 +59,7 @@ export default class AdminUserLocationHandler {
}
static async handleEditUserCountry(callbackQuery) {
if (!this.isAdmin(callbackQuery.from.id)) {
if (!isAdmin(callbackQuery.from.id)) {
return;
}
@@ -98,7 +95,7 @@ export default class AdminUserLocationHandler {
}
static async handleEditUserCity(callbackQuery) {
if (!this.isAdmin(callbackQuery.from.id)) {
if (!isAdmin(callbackQuery.from.id)) {
return;
}
@@ -134,7 +131,7 @@ export default class AdminUserLocationHandler {
}
static async handleEditUserDistrict(callbackQuery) {
if (!this.isAdmin(callbackQuery.from.id)) {
if (!isAdmin(callbackQuery.from.id)) {
return;
}

View File

@@ -8,6 +8,7 @@ const csvPath = path.join(os.tmpdir(), 'wallets_export.csv');
import bot from "../../context/bot.js";
import config from '../../config/config.js';
import { isAdmin } from '../../middleware/auth.js';
import WalletService from '../../services/walletService.js';
import WalletUtils from '../../utils/walletUtils.js';
import Validators from '../../utils/validators.js';
@@ -36,15 +37,13 @@ export default class AdminWalletsHandler {
}
// Метод для проверки, является ли пользователь администратором
static isAdmin(userId) {
return config.ADMIN_IDS.includes(userId.toString());
}
// (используется общая функция из middleware/auth.js)
static async handleWalletManagement(msg) {
const chatId = msg.chat.id;
// Проверяем, является ли пользователь администратором
if (!this.isAdmin(msg.from.id)) {
if (!isAdmin(msg.from.id)) {
await bot.sendMessage(chatId, 'Unauthorized access.');
return;
}
@@ -133,24 +132,7 @@ export default class AdminWalletsHandler {
const balance = wallet.balance || 0;
// Рассчитываем значение в долларах
let usdValue = 0;
switch (baseType) {
case 'BTC':
usdValue = balance * prices.btc;
break;
case 'LTC':
usdValue = balance * prices.ltc;
break;
case 'ETH':
usdValue = balance * prices.eth;
break;
case 'USDT':
usdValue = balance; // USDT привязан к доллару
break;
case 'USDC':
usdValue = balance; // USDC привязан к доллару
break;
}
const usdValue = WalletUtils.convertToUsd(wallet.wallet_type, balance, prices);
// Формируем строку для кошелька
walletList += `💰 ${baseType}${archivedDate}\n`;
@@ -208,24 +190,7 @@ export default class AdminWalletsHandler {
const balance = wallet.balance || 0;
// Рассчитываем значение в долларах
let usdValue = 0;
switch (baseType) {
case 'BTC':
usdValue = balance * prices.btc;
break;
case 'LTC':
usdValue = balance * prices.ltc;
break;
case 'ETH':
usdValue = balance * prices.eth;
break;
case 'USDT':
usdValue = balance; // USDT привязан к доллару
break;
case 'USDC':
usdValue = balance; // USDC привязан к доллару
break;
}
const usdValue = WalletUtils.convertToUsd(wallet.wallet_type, balance, prices);
totalBalance += usdValue;
}
@@ -429,24 +394,7 @@ export default class AdminWalletsHandler {
const balance = wallet.balance || 0;
// Рассчитываем значение в долларах
let usdValue = 0;
switch (baseType) {
case 'BTC':
usdValue = balance * prices.btc;
break;
case 'LTC':
usdValue = balance * prices.ltc;
break;
case 'ETH':
usdValue = balance * prices.eth;
break;
case 'USDT':
usdValue = balance; // USDT привязан к доллару
break;
case 'USDC':
usdValue = balance; // USDC привязан к доллару
break;
}
const usdValue = WalletUtils.convertToUsd(wallet.wallet_type, balance, prices);
// Форматируем дату архивации (если кошелек архивный)
let archivedDate = '';

View File

@@ -728,11 +728,6 @@ export default class UserWalletsHandler {
}
// Helper methods
static getBaseWalletType(walletType) {
if (walletType.includes('ERC-20')) return 'ETH';
return walletType;
}
static getWalletAddress(wallets, walletType) {
if (walletType.includes('ERC-20')) return wallets.ETH.address;
if (walletType === 'BTC') return wallets.BTC.address;
@@ -749,9 +744,4 @@ export default class UserWalletsHandler {
if (walletType === 'ETH') return 'Ethereum Network';
return 'Unknown Network';
}
static getBaseWalletType(walletType) {
// Убираем суффиксы, такие как ERC-20, TRC-20 и т.д.
return walletType.replace(/ (ERC-20|TRC-20)$/, '');
}
}

5
src/middleware/auth.js Normal file
View File

@@ -0,0 +1,5 @@
import config from '../config/config.js';
export function isAdmin(userId) {
return config.ADMIN_IDS.includes(userId.toString());
}

View File

@@ -4,11 +4,6 @@ import db from "../config/database.js";
import WalletUtils from "../utils/walletUtils.js";
export default class Wallet {
static getBaseWalletType(walletType) {
if (walletType.includes('ERC-20')) return 'ETH';
return walletType;
}
static async getArchivedWallets(userId) {
const archivedWallets = await db.allAsync(`
SELECT * FROM crypto_wallets WHERE user_id = ? AND wallet_type LIKE '%_%'
@@ -60,7 +55,7 @@ export default class Wallet {
let totalUsdBalance = 0;
for (const [type, balance] of Object.entries(balances)) {
const baseType = this.getBaseWalletType(type);
const baseType = WalletUtils.getBaseWalletType(type);
const wallet = activeWallets.wallets.find(w =>
w.wallet_type === baseType ||
(type.includes('ERC-20') && w.wallet_type === 'ETH')
@@ -92,7 +87,7 @@ export default class Wallet {
let totalUsdBalance = 0;
for (const [type, balance] of Object.entries(balances)) {
const baseType = this.getBaseWalletType(type);
const baseType = WalletUtils.getBaseWalletType(type);
const wallet = archiveWallets.wallets.find(w =>
w.wallet_type === baseType ||
(type.includes('ERC-20') && w.wallet_type.startsWith('ETH'))

View File

@@ -150,9 +150,7 @@ class UserService {
// Пересчитываем балансы в доллары
let totalCryptoBalance = 0;
for (const wallet of cryptoBalances) {
const baseType = WalletUtils.getBaseWalletType(wallet.wallet_type); // Убираем суффиксы
const rate = prices[baseType.toLowerCase()] || 0; // Получаем курс для базового типа
totalCryptoBalance += wallet.balance * rate;
totalCryptoBalance += WalletUtils.convertToUsd(wallet.wallet_type, wallet.balance, prices);
}
// Получаем сумму всех покупок в крипте

View File

@@ -35,26 +35,8 @@ class WalletService {
let totalBalance = 0;
for (const wallet of wallets) {
const baseType = WalletUtils.getBaseWalletType(wallet.wallet_type);
const balance = wallet.balance || 0;
switch (baseType) {
case 'BTC':
totalBalance += balance * prices.btc;
break;
case 'LTC':
totalBalance += balance * prices.ltc;
break;
case 'ETH':
totalBalance += balance * prices.eth;
break;
case 'USDT':
totalBalance += balance; // USDT is 1:1 with USD
break;
case 'USDC':
totalBalance += balance; // USDC is 1:1 with USD
break;
}
totalBalance += WalletUtils.convertToUsd(wallet.wallet_type, balance, prices);
}
return totalBalance;
@@ -77,26 +59,8 @@ class WalletService {
let totalBalance = 0;
for (const wallet of wallets) {
const baseType = WalletUtils.getBaseWalletType(wallet.wallet_type);
const balance = wallet.balance || 0;
switch (baseType) {
case 'BTC':
totalBalance += balance * prices.btc;
break;
case 'LTC':
totalBalance += balance * prices.ltc;
break;
case 'ETH':
totalBalance += balance * prices.eth;
break;
case 'USDT':
totalBalance += balance; // USDT is 1:1 with USD
break;
case 'USDC':
totalBalance += balance; // USDC is 1:1 with USD
break;
}
totalBalance += WalletUtils.convertToUsd(wallet.wallet_type, balance, prices);
}
return totalBalance;

View File

@@ -76,20 +76,21 @@ export default class WalletUtils {
}
static getBaseWalletType(walletType) {
// Убираем суффикс ERC-20, если он есть
if (walletType.includes('ERC-20')) {
return 'ETH';
}
// Убираем суффикс с таймштампом (например, USDT_1735846098129 -> USDT)
if (walletType.includes('_')) {
return walletType.split('_')[0];
}
// Возвращаем исходный тип, если это не ERC-20 и не архивный кошелек
return walletType;
}
static convertToUsd(walletType, balance, prices) {
const baseType = WalletUtils.getBaseWalletType(walletType);
const rate = prices[baseType.toLowerCase()] || 0;
return balance * rate;
}
static async getCryptoPrices() {
// Если кеш актуален, возвращаем его
if (cryptoPricesCache && Date.now() - cacheTimestamp < CACHE_TTL) {
@@ -291,30 +292,13 @@ export default class WalletUtils {
`, [this.userId]);
for (const wallet of wallets) {
const [baseType] = wallet.wallet_type.split('_'); // Учитываем только базовый тип
const baseType = WalletUtils.getBaseWalletType(wallet.wallet_type);
const balance = wallet.balance || 0;
switch (baseType) {
case 'BTC':
balances.BTC.amount += balance;
balances.BTC.usdValue += balance * prices.btc;
break;
case 'LTC':
balances.LTC.amount += balance;
balances.LTC.usdValue += balance * prices.ltc;
break;
case 'ETH':
balances.ETH.amount += balance;
balances.ETH.usdValue += balance * prices.eth;
break;
case 'USDT':
balances.USDT.amount += balance;
balances.USDT.usdValue += balance;
break;
case 'USDC':
balances.USDC.amount += balance;
balances.USDC.usdValue += balance;
break;
const usdValue = WalletUtils.convertToUsd(wallet.wallet_type, balance, prices);
if (balances[baseType]) {
balances[baseType].amount += balance;
balances[baseType].usdValue += usdValue;
}
}