Бэкенд для обработки форм обратной связи с отправкой в Telegram.
- Node.js 18+
- pnpm
- Docker (опционально)
- Клонируйте репозиторий:
git clone <repository-url>
cd feedback- Установите зависимости:
pnpm install- Создайте
.envфайл:
cp .env.example .env- Настройте переменные окружения в
.env:
# Application Configuration
PORT=3000
NODE_ENV=development
# Telegram Bot Configuration
TELEGRAM_BOT_TOKEN=your_telegram_bot_token_here
TELEGRAM_CHAT_ID=your_telegram_chat_id_here
# Security Configuration
THROTTLE_TTL=60
THROTTLE_LIMIT=5
ALLOWED_ORIGINS=http://localhost:3000,http://mysite.com
# Google reCAPTCHA credentials
RECAPTCHA_SITE_KEY=your_recaptcha_site_key_here
RECAPTCHA_SECRET_KEY=your_recaptcha_secret_key_herepnpm start:devpnpm build
pnpm start:proddocker-compose upПосле запуска приложение доступно по:
- API: http://localhost:3000/api
- Swagger: http://localhost:3000/api/docs
GET /api/health
Проверка состояния приложения.
POST /api/feedback
Пример запроса:
{
"name": "Иван Иванов",
"contact": "ivan@example.com",
"message": "Тестовое сообщение"
}Пример ответа:
{
"message": "Feedback submitted successfully",
"data": {
"id": "uuid",
"name": "Иван Иванов",
"contact": "ivan@example.com",
"message": "Тестовое сообщение",
"createdAt": "2024-01-01T12:00:00.000Z"
}
}- Зарегистрируйте сайт в Google reCAPTCHA Admin Console
- Получите Site Key и Secret Key
- Добавьте ключи в
.envфайл:
RECAPTCHA_SITE_KEY=your_recaptcha_site_key_here
RECAPTCHA_SECRET_KEY=your_recaptcha_secret_key_hereЕсли внешний фронтенд находится на другом домене, настройте CORS:
ALLOWED_ORIGINS=http://your-frontend.com,http://localhost:3000Для отправки формы с внешнего HTML/JS фронтенда необходимо выполнить следующие шаги:
Сначала необходимо получить CSRF токен, который будет использоваться для защиты формы. Для этого отправьте GET запрос к эндпоинту /api/csrf:
// Получение CSRF токена
async function getCsrfToken() {
try {
const response = await fetch('http://localhost:3000/api/csrf', {
method: 'GET',
credentials: 'include', // Важно! Для передачи cookies
});
if (!response.ok) {
throw new Error('Failed to get CSRF token');
}
return response;
} catch (error) {
console.error('Error getting CSRF token:', error);
throw error;
}
}После получения CSRF токена можно отправлять форму. Пример реализации:
// Отправка формы обратной связи
async function submitFeedback(formData) {
try {
// Получаем CSRF токен
await getCsrfToken();
const response = await fetch('http://localhost:3000/api/feedback', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token':
document.cookie
.split('; ')
.find((row) => row.startsWith('csrf_token='))
?.split('=')[1] || '',
},
credentials: 'include', // Важно! Для передачи cookies
body: JSON.stringify(formData),
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
console.log('Feedback submitted:', result);
return result;
} catch (error) {
console.error('Error submitting feedback:', error);
throw error;
}
}
// Пример использования
const feedbackForm = {
name: 'Иван Иванов',
contact: 'ivan@example.com',
message: 'Тестовое сообщение',
// Если используется reCAPTCHA
recaptchaToken: 'your_recaptcha_token_here',
};
submitFeedback(feedbackForm)
.then((result) => {
// Обработка успешной отправки
alert('Форма успешно отправлена!');
})
.catch((error) => {
// Обработка ошибки
alert('Ошибка при отправке формы: ' + error.message);
});Если на форме используется reCAPTCHA, необходимо:
- Добавить скрипт reCAPTCHA на страницу:
<script src="https://www.google.com/recaptcha/api.js" async defer></script>- Добавить элемент reCAPTCHA:
<div class="g-recaptcha" data-sitekey="your_recaptcha_site_key_here"></div>- Получить токен reCAPTCHA перед отправкой формы:
function getRecaptchaToken() {
return new Promise((resolve, reject) => {
grecaptcha.ready(() => {
grecaptcha
.execute('your_recaptcha_site_key_here', { action: 'submit' })
.then((token) => {
resolve(token);
})
.catch((error) => {
reject(error);
});
});
});
}
// Использование при отправке формы
async function submitWithRecaptcha(formData) {
try {
const recaptchaToken = await getRecaptchaToken();
formData.recaptchaToken = recaptchaToken;
return await submitFeedback(formData);
} catch (error) {
console.error('reCAPTCHA error:', error);
throw error;
}
}Важно корректно обрабатывать возможные ошибки:
async function submitFeedback(formData) {
try {
await getCsrfToken();
const response = await fetch('http://localhost:3000/api/feedback', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token':
document.cookie
.split('; ')
.find((row) => row.startsWith('csrf_token='))
?.split('=')[1] || '',
},
credentials: 'include',
body: JSON.stringify(formData),
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(
errorData.message || 'Произошла ошибка при отправке формы',
);
}
return await response.json();
} catch (error) {
console.error('Submission error:', error);
throw error;
}
}