// routes/api.js // API маршруты для калькуляторов const express = require('express'); const multer = require('multer'); const { getCalculator, hasCalculator } = require('../calculators'); const { sendTelegramMessage } = require('../lib/telegram'); const { validateRequiredFields, validateNumericFields } = require('../lib/validator'); const router = express.Router(); // Настраиваем multer (храним файлы в памяти) const storage = multer.memoryStorage(); const upload = multer({ storage }); /** * Обработчик POST /api/submit/:calculatorType * Принимает данные калькулятора и отправляет результат в Telegram */ router.post( '/submit/:calculatorType', upload.single('photo'), async (req, res) => { try { const { calculatorType } = req.params; const { chat_id, ...formData } = req.body; const photoFile = req.file; // Проверяем, существует ли калькулятор if (!hasCalculator(calculatorType)) { return res.status(404).json({ error: `Калькулятор "${calculatorType}" не найден`, }); } // Получаем модуль калькулятора const calculator = getCalculator(calculatorType); // Валидация обязательных полей const requiredValidation = validateRequiredFields( { chat_id, ...formData }, ['chat_id', ...calculator.getRequiredFields()] ); if (!requiredValidation.valid) { return res.status(400).json({ error: requiredValidation.error }); } // Валидация числовых полей const numericValidation = validateNumericFields( formData, calculator.getNumericFields() ); if (!numericValidation.valid) { return res.status(400).json({ error: numericValidation.error }); } // Подготавливаем данные для расчёта const calculationData = prepareCalculationData(formData, calculator.fieldSchema); // Выполняем расчёт const calculationResult = calculator.calculate(calculationData); // Вычисляем финальную цену и цену за 100г (если применимо) const markup = parseFloat(formData.markup || 0); const totalCost = calculationResult.total; const finalPrice = totalCost * (1 + markup / 100); const weight = parseFloat(formData.weight || 0); const pricePer100g = weight > 0 ? (finalPrice / weight) * 100 : 0; // Формируем сообщение для Telegram const messageData = { ...formData, totalCost, finalPrice, pricePer100g, }; const telegramMessage = calculator.formatMessage(messageData, calculationResult); // Отправляем в Telegram try { await sendTelegramMessage( req.app.locals.bot, chat_id, telegramMessage, photoFile ? photoFile.buffer : null ); res.sendStatus(200); } catch (telegramError) { console.error('Ошибка при отправке в Telegram:', telegramError); res.status(500).json({ error: 'Ошибка при отправке сообщения в Telegram', details: telegramError.message, }); } } catch (err) { console.error('Ошибка в /api/submit:', err); res.status(500).json({ error: 'Внутренняя ошибка сервера', details: err.message, }); } } ); /** * Подготавливает данные для расчёта из формы * @param {Object} formData - Данные из формы * @param {Array} fieldSchema - Схема полей калькулятора * @returns {Object} Подготовленные данные для расчёта */ function prepareCalculationData(formData, fieldSchema) { const data = {}; for (const field of fieldSchema) { if (formData[field.name] !== undefined) { const value = formData[field.name]; if (field.type === 'number') { data[field.name] = parseFloat(value) || 0; } else { data[field.name] = value; } } } // Специфичная обработка для мыла (упаковка) if (formData.box !== undefined || formData.filler !== undefined || formData.ribbon !== undefined || formData.labelValue !== undefined) { data.packaging = { box: parseFloat(formData.box || 0), filler: parseFloat(formData.filler || 0), ribbon: parseFloat(formData.ribbon || 0), label: parseFloat(formData.labelValue || 0), }; } return data; } module.exports = router;