Bump version to 1.1.1: Fix Telegram webhook handling and chat_id saving
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 1m33s

- Fix TelegramUpdate struct to handle message and edited_message properly
- Fix chat_id saving for messages without text
- Add comprehensive logging for webhook registration
- Improve error handling and diagnostics
This commit is contained in:
poignatov
2025-12-31 19:39:01 +03:00
parent 7398918bc0
commit f8aa81f963
4 changed files with 118 additions and 19 deletions

63
.env.bak Normal file
View File

@@ -0,0 +1,63 @@
# ============================================
# Единый файл конфигурации для всех проектов
# Backend и Play-Life-Web
# ============================================
# ============================================
# Database Configuration
# ============================================
DB_HOST=localhost
DB_PORT=5432
DB_USER=playeng
DB_PASSWORD=playeng
DB_NAME=playeng
# ============================================
# Backend Server Configuration
# ============================================
# Порт для backend сервера (по умолчанию: 8080)
# В production всегда используется порт 8080 внутри контейнера
PORT=8080
# ============================================
# Play Life Web Configuration
# ============================================
# Порт для frontend приложения play-life-web
WEB_PORT=3001
# ============================================
# Telegram Bot Configuration (optional)
# ============================================
# Get token from @BotFather in Telegram: https://t.me/botfather
# To get chat ID: send a message to your bot, then visit: https://api.telegram.org/bot<YOUR_BOT_TOKEN>/getUpdates
# Look for "chat":{"id":123456789} - that number is your chat ID
TELEGRAM_BOT_TOKEN=your_telegram_bot_token_here
TELEGRAM_CHAT_ID=123456789
# Base URL для автоматической настройки webhook
# Примеры:
# - Для production с HTTPS: https://your-domain.com
# - Для локальной разработки с ngrok: https://abc123.ngrok.io
# - Для прямого доступа на нестандартном порту: http://your-server:8080
# Webhook будет настроен автоматически при старте сервера на: <TELEGRAM_WEBHOOK_BASE_URL>/webhook/telegram
# Если не указан, webhook нужно настраивать вручную
TELEGRAM_WEBHOOK_BASE_URL=https://your-domain.com
# ============================================
# Todoist Webhook Configuration (optional)
# ============================================
# Секрет для проверки подлинности webhook от Todoist
# Если задан, все запросы должны содержать заголовок X-Todoist-Webhook-Secret с этим значением
# Оставьте пустым, если не хотите использовать проверку секрета
TODOIST_WEBHOOK_SECRET=
# ============================================
# Scheduler Configuration
# ============================================
# Часовой пояс для планировщика задач (например: Europe/Moscow, America/New_York, UTC)
# Используется для:
# - Автоматической фиксации целей на неделю каждый понедельник в 6:00
# - Отправки ежедневного отчёта в 23:59
# ВАЖНО: Укажите правильный часовой пояс, иначе задачи будут срабатывать в UTC!
# Список доступных часовых поясов: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
TIMEZONE=Europe/Moscow

View File

@@ -1,2 +1,2 @@
1.1.0 1.1.1

View File

@@ -182,8 +182,9 @@ type TelegramWebhook struct {
// TelegramUpdate - структура для Telegram webhook (обычно это Update объект) // TelegramUpdate - структура для Telegram webhook (обычно это Update объект)
type TelegramUpdate struct { type TelegramUpdate struct {
UpdateID int `json:"update_id"` UpdateID int `json:"update_id"`
Message TelegramMessage `json:"message"` Message *TelegramMessage `json:"message,omitempty"`
EditedMessage *TelegramMessage `json:"edited_message,omitempty"`
} }
type App struct { type App struct {
@@ -2374,20 +2375,22 @@ func main() {
} }
// Пытаемся настроить webhook автоматически при старте, если есть base URL и bot token в БД // Пытаемся настроить webhook автоматически при старте, если есть base URL и bot token в БД
// Это опционально - основная регистрация происходит при сохранении токена через UI
webhookBaseURL := getEnv("WEBHOOK_BASE_URL", "") webhookBaseURL := getEnv("WEBHOOK_BASE_URL", "")
if webhookBaseURL != "" { if webhookBaseURL != "" {
integration, err := app.getTelegramIntegration() integration, err := app.getTelegramIntegration()
if err == nil && integration.BotToken != nil && *integration.BotToken != "" { if err == nil && integration.BotToken != nil && *integration.BotToken != "" {
webhookURL := strings.TrimRight(webhookBaseURL, "/") + "/webhook/telegram" webhookURL := strings.TrimRight(webhookBaseURL, "/") + "/webhook/telegram"
log.Printf("Attempting to setup Telegram webhook at startup. WEBHOOK_BASE_URL='%s'", webhookBaseURL)
if err := setupTelegramWebhook(*integration.BotToken, webhookURL); err != nil { if err := setupTelegramWebhook(*integration.BotToken, webhookURL); err != nil {
log.Printf("Warning: Failed to setup Telegram webhook at startup: %v. Webhook will be configured when user saves bot token.", err) log.Printf("Warning: Failed to setup Telegram webhook at startup: %v. Webhook will be configured when user saves bot token.", err)
} else { } else {
log.Printf("Telegram webhook configured successfully at startup: %s", webhookURL) log.Printf("SUCCESS: Telegram webhook configured successfully at startup: %s", webhookURL)
} }
} else { } else {
log.Printf("Telegram bot token not found in database. Webhook will be configured when user saves bot token.") log.Printf("Telegram bot token not found in database. Webhook will be configured when user saves bot token.")
} }
} else {
log.Printf("WEBHOOK_BASE_URL not set. Webhook will be configured when user saves bot token.")
} }
// Инициализируем БД для play-life проекта // Инициализируем БД для play-life проекта
@@ -2469,6 +2472,7 @@ func getMapKeys(m map[string]interface{}) []string {
// setupTelegramWebhook настраивает webhook для Telegram бота // setupTelegramWebhook настраивает webhook для Telegram бота
func setupTelegramWebhook(botToken, webhookURL string) error { func setupTelegramWebhook(botToken, webhookURL string) error {
apiURL := fmt.Sprintf("https://api.telegram.org/bot%s/setWebhook", botToken) apiURL := fmt.Sprintf("https://api.telegram.org/bot%s/setWebhook", botToken)
log.Printf("Setting up Telegram webhook: apiURL=%s, webhookURL=%s", apiURL, webhookURL)
payload := map[string]string{ payload := map[string]string{
"url": webhookURL, "url": webhookURL,
@@ -2486,12 +2490,15 @@ func setupTelegramWebhook(botToken, webhookURL string) error {
resp, err := client.Post(apiURL, "application/json", bytes.NewBuffer(jsonData)) resp, err := client.Post(apiURL, "application/json", bytes.NewBuffer(jsonData))
if err != nil { if err != nil {
log.Printf("ERROR: Failed to send webhook setup request: %v", err)
return fmt.Errorf("failed to send webhook setup request: %w", err) return fmt.Errorf("failed to send webhook setup request: %w", err)
} }
defer resp.Body.Close() defer resp.Body.Close()
bodyBytes, _ := io.ReadAll(resp.Body)
log.Printf("Telegram API response: status=%d, body=%s", resp.StatusCode, string(bodyBytes))
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
bodyBytes, _ := io.ReadAll(resp.Body)
return fmt.Errorf("telegram API returned status %d: %s", resp.StatusCode, string(bodyBytes)) return fmt.Errorf("telegram API returned status %d: %s", resp.StatusCode, string(bodyBytes))
} }
@@ -4100,24 +4107,51 @@ func (a *App) telegramWebhookHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
// Сохраняем chat_id при первом сообщении // Определяем, какое сообщение использовать (message или edited_message)
if update.Message.Chat.ID != 0 { var message *TelegramMessage
chatIDStr := strconv.FormatInt(update.Message.Chat.ID, 10) if update.Message != nil {
message = update.Message
log.Printf("Telegram webhook received: update_id=%d, message type=message", update.UpdateID)
} else if update.EditedMessage != nil {
message = update.EditedMessage
log.Printf("Telegram webhook received: update_id=%d, message type=edited_message", update.UpdateID)
} else {
log.Printf("Telegram webhook received: update_id=%d, but no message or edited_message found", update.UpdateID)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{
"message": "No message found in update",
})
return
}
log.Printf("Telegram webhook: message present, chat_id=%d", message.Chat.ID)
// Сохраняем chat_id при первом сообщении (даже если нет текста)
if message.Chat.ID != 0 {
chatIDStr := strconv.FormatInt(message.Chat.ID, 10)
log.Printf("Processing chat_id: %s", chatIDStr)
integration, err := a.getTelegramIntegration() integration, err := a.getTelegramIntegration()
if err == nil { if err != nil {
log.Printf("Error getting telegram integration: %v", err)
} else {
// Сохраняем chat_id, если его еще нет // Сохраняем chat_id, если его еще нет
if integration.ChatID == nil || *integration.ChatID == "" { if integration.ChatID == nil || *integration.ChatID == "" {
log.Printf("Attempting to save chat_id: %s", chatIDStr)
if err := a.saveTelegramChatID(chatIDStr); err != nil { if err := a.saveTelegramChatID(chatIDStr); err != nil {
log.Printf("Warning: Failed to save chat_id: %v", err) log.Printf("Warning: Failed to save chat_id: %v", err)
} else { } else {
log.Printf("Saved chat_id from first message: %s", chatIDStr) log.Printf("Successfully saved chat_id from first message: %s", chatIDStr)
} }
} else {
log.Printf("Chat_id already exists in database: %s", *integration.ChatID)
} }
} }
} else {
log.Printf("Warning: message.Chat.ID is 0, cannot save chat_id")
} }
// Проверяем, что есть message // Проверяем, что есть текст в сообщении
if update.Message.Text == "" { if message.Text == "" {
log.Printf("Telegram webhook: no text in message") log.Printf("Telegram webhook: no text in message")
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{ json.NewEncoder(w).Encode(map[string]string{
@@ -4126,8 +4160,8 @@ func (a *App) telegramWebhookHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
fullText := update.Message.Text fullText := message.Text
entities := update.Message.Entities entities := message.Entities
if entities == nil { if entities == nil {
entities = []TelegramEntity{} entities = []TelegramEntity{}
} }
@@ -4278,16 +4312,18 @@ func (a *App) updateTelegramIntegrationHandler(w http.ResponseWriter, r *http.Re
// Настраиваем webhook автоматически при сохранении токена // Настраиваем webhook автоматически при сохранении токена
webhookBaseURL := getEnv("WEBHOOK_BASE_URL", "") webhookBaseURL := getEnv("WEBHOOK_BASE_URL", "")
log.Printf("Attempting to setup Telegram webhook. WEBHOOK_BASE_URL='%s'", webhookBaseURL)
if webhookBaseURL != "" { if webhookBaseURL != "" {
webhookURL := strings.TrimRight(webhookBaseURL, "/") + "/webhook/telegram" webhookURL := strings.TrimRight(webhookBaseURL, "/") + "/webhook/telegram"
log.Printf("Setting up Telegram webhook: URL=%s", webhookURL)
if err := setupTelegramWebhook(req.BotToken, webhookURL); err != nil { if err := setupTelegramWebhook(req.BotToken, webhookURL); err != nil {
log.Printf("Warning: Failed to setup Telegram webhook: %v", err) log.Printf("ERROR: Failed to setup Telegram webhook: %v", err)
// Не возвращаем ошибку, так как токен уже сохранен // Не возвращаем ошибку, так как токен уже сохранен
} else { } else {
log.Printf("Telegram webhook configured successfully: %s", webhookURL) log.Printf("SUCCESS: Telegram webhook configured successfully: %s", webhookURL)
} }
} else { } else {
log.Printf("Warning: WEBHOOK_BASE_URL not set. Webhook will not be configured automatically.") log.Printf("WARNING: WEBHOOK_BASE_URL not set. Webhook will not be configured automatically.")
} }
integration, err := a.getTelegramIntegration() integration, err := a.getTelegramIntegration()

View File

@@ -1,6 +1,6 @@
{ {
"name": "play-life-web", "name": "play-life-web",
"version": "1.1.0", "version": "1.1.1",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",