From edc29fbd9776e763373d80661a6c2e1b41fbac37 Mon Sep 17 00:00:00 2001 From: poignatov Date: Thu, 1 Jan 2026 18:50:55 +0300 Subject: [PATCH] v2.0.4: Fix webhook error handling and logging - Webhooks now return 200 OK even on errors (to prevent retries) - Improved error handling with proper JSON responses - Enhanced logging for webhook debugging - Supervisor logs now visible in docker logs (stdout/stderr) - Fixed TodoistIntegration error display in UI --- VERSION | 2 +- play-life-backend/main.go | 126 +++++++++++++++--- play-life-web/package.json | 2 +- .../src/components/TodoistIntegration.jsx | 22 ++- supervisord.conf | 10 +- 5 files changed, 140 insertions(+), 22 deletions(-) diff --git a/VERSION b/VERSION index 50ffc5a..2165f8f 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.0.3 +2.0.4 diff --git a/play-life-backend/main.go b/play-life-backend/main.go index e3008d0..369aa5d 100644 --- a/play-life-backend/main.go +++ b/play-life-backend/main.go @@ -4886,6 +4886,7 @@ func (a *App) todoistWebhookHandler(w http.ResponseWriter, r *http.Request) { log.Printf("=== Todoist Webhook Request ===") log.Printf("Method: %s", r.Method) log.Printf("URL: %s", r.URL.String()) + log.Printf("Path: %s", r.URL.Path) log.Printf("RemoteAddr: %s", r.RemoteAddr) if r.Method == "OPTIONS" { @@ -4899,9 +4900,16 @@ func (a *App) todoistWebhookHandler(w http.ResponseWriter, r *http.Request) { // Извлекаем токен из URL vars := mux.Vars(r) token := vars["token"] + log.Printf("Extracted token from URL: '%s'", token) if token == "" { log.Printf("Todoist webhook: missing token in URL") - sendErrorWithCORS(w, "Missing webhook token", http.StatusBadRequest) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(map[string]interface{}{ + "ok": false, + "error": "Missing webhook token", + "message": "Token required in URL", + }) return } @@ -4915,11 +4923,23 @@ func (a *App) todoistWebhookHandler(w http.ResponseWriter, r *http.Request) { if err == sql.ErrNoRows { log.Printf("Todoist webhook: invalid token: %s", token) - sendErrorWithCORS(w, "Invalid webhook token", http.StatusUnauthorized) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(map[string]interface{}{ + "ok": false, + "error": "Invalid webhook token", + "message": "Token not found", + }) return } else if err != nil { log.Printf("Error finding user by webhook token: %v", err) - sendErrorWithCORS(w, "Internal server error", http.StatusInternalServerError) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(map[string]interface{}{ + "ok": false, + "error": "Internal server error", + "message": "Database error", + }) return } @@ -4929,7 +4949,13 @@ func (a *App) todoistWebhookHandler(w http.ResponseWriter, r *http.Request) { bodyBytes, err := io.ReadAll(r.Body) if err != nil { log.Printf("Error reading request body: %v", err) - sendErrorWithCORS(w, "Error reading request body", http.StatusBadRequest) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(map[string]interface{}{ + "ok": false, + "error": "Error reading request body", + "message": "Failed to read request", + }) return } @@ -4948,7 +4974,13 @@ func (a *App) todoistWebhookHandler(w http.ResponseWriter, r *http.Request) { log.Printf("Provided secret in header: %v (length: %d)", providedSecret != "", len(providedSecret)) if providedSecret != todoistWebhookSecret { log.Printf("Invalid Todoist webhook secret provided (expected length: %d, provided length: %d)", len(todoistWebhookSecret), len(providedSecret)) - sendErrorWithCORS(w, "Unauthorized", http.StatusUnauthorized) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(map[string]interface{}{ + "ok": false, + "error": "Unauthorized", + "message": "Invalid webhook secret", + }) return } log.Printf("Webhook secret validated successfully") @@ -4959,7 +4991,13 @@ func (a *App) todoistWebhookHandler(w http.ResponseWriter, r *http.Request) { if err := json.NewDecoder(r.Body).Decode(&webhook); err != nil { log.Printf("Error decoding Todoist webhook: %v", err) log.Printf("Failed to parse body as JSON: %s", string(bodyBytes)) - sendErrorWithCORS(w, "Invalid request body", http.StatusBadRequest) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(map[string]interface{}{ + "ok": false, + "error": "Invalid request body", + "message": "Failed to parse JSON", + }) return } @@ -5025,7 +5063,13 @@ func (a *App) todoistWebhookHandler(w http.ResponseWriter, r *http.Request) { log.Printf("ERROR: Todoist webhook: no content or description found in event_data") log.Printf(" title='%s' (empty: %v), description='%s' (empty: %v)", title, title == "", description, description == "") log.Printf("Available keys in event_data: %v", getMapKeys(webhook.EventData)) - sendErrorWithCORS(w, "Missing 'content' or 'description' in event_data", http.StatusBadRequest) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(map[string]interface{}{ + "ok": false, + "error": "Missing 'content' or 'description' in event_data", + "message": "No content to process", + }) return } @@ -5038,7 +5082,13 @@ func (a *App) todoistWebhookHandler(w http.ResponseWriter, r *http.Request) { response, err := a.processMessageWithoutTelegram(combinedText, userIDPtr) if err != nil { log.Printf("ERROR processing Todoist message: %v", err) - sendErrorWithCORS(w, err.Error(), http.StatusInternalServerError) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(map[string]interface{}{ + "ok": false, + "error": err.Error(), + "message": "Error processing message", + }) return } @@ -5072,14 +5122,22 @@ func (a *App) todoistWebhookHandler(w http.ResponseWriter, r *http.Request) { log.Printf("=== Todoist Webhook Request Completed Successfully ===") w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(map[string]interface{}{ + "ok": true, "message": "Task processed successfully", "result": response, }) } func (a *App) telegramWebhookHandler(w http.ResponseWriter, r *http.Request) { + log.Printf("=== Telegram Webhook Request ===") + log.Printf("Method: %s", r.Method) + log.Printf("URL: %s", r.URL.String()) + log.Printf("Path: %s", r.URL.Path) + if r.Method == "OPTIONS" { + log.Printf("OPTIONS request, returning OK") setCORSHeaders(w) w.WriteHeader(http.StatusOK) return @@ -5089,9 +5147,16 @@ func (a *App) telegramWebhookHandler(w http.ResponseWriter, r *http.Request) { // Извлекаем токен из URL vars := mux.Vars(r) token := vars["token"] + log.Printf("Extracted token from URL: '%s'", token) if token == "" { log.Printf("Telegram webhook: missing token in URL") - sendErrorWithCORS(w, "Missing webhook token", http.StatusBadRequest) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(map[string]interface{}{ + "ok": false, + "error": "Missing webhook token", + "message": "Token required in URL", + }) return } @@ -5105,11 +5170,24 @@ func (a *App) telegramWebhookHandler(w http.ResponseWriter, r *http.Request) { if err == sql.ErrNoRows { log.Printf("Telegram webhook: invalid token: %s", token) - sendErrorWithCORS(w, "Invalid webhook token", http.StatusUnauthorized) + // Возвращаем 200 OK, но логируем ошибку (не хотим, чтобы Telegram повторял запрос) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(map[string]interface{}{ + "ok": false, + "error": "Invalid webhook token", + "message": "Token not found", + }) return } else if err != nil { log.Printf("Error finding user by webhook token: %v", err) - sendErrorWithCORS(w, "Internal server error", http.StatusInternalServerError) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(map[string]interface{}{ + "ok": false, + "error": "Internal server error", + "message": "Database error", + }) return } @@ -5119,7 +5197,14 @@ func (a *App) telegramWebhookHandler(w http.ResponseWriter, r *http.Request) { var update TelegramUpdate if err := json.NewDecoder(r.Body).Decode(&update); err != nil { log.Printf("Error decoding Telegram webhook: %v", err) - sendErrorWithCORS(w, "Invalid request body", http.StatusBadRequest) + // Возвращаем 200 OK, чтобы Telegram не повторял запрос + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(map[string]interface{}{ + "ok": false, + "error": "Invalid request body", + "message": "Failed to decode webhook", + }) return } @@ -5134,7 +5219,9 @@ func (a *App) telegramWebhookHandler(w http.ResponseWriter, r *http.Request) { } 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{ + w.WriteHeader(http.StatusOK) // Возвращаем 200 OK для Telegram + json.NewEncoder(w).Encode(map[string]interface{}{ + "ok": true, "message": "No message found in update", }) return @@ -5173,7 +5260,9 @@ func (a *App) telegramWebhookHandler(w http.ResponseWriter, r *http.Request) { if message.Text == "" { log.Printf("Telegram webhook: no text in message") w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(map[string]string{ + w.WriteHeader(http.StatusOK) // Возвращаем 200 OK для Telegram + json.NewEncoder(w).Encode(map[string]interface{}{ + "ok": true, "message": "No text in message, ignored", }) return @@ -5191,7 +5280,13 @@ func (a *App) telegramWebhookHandler(w http.ResponseWriter, r *http.Request) { response, err := a.processTelegramMessage(fullText, entities, userIDPtr) if err != nil { log.Printf("Error processing Telegram message: %v", err) - sendErrorWithCORS(w, err.Error(), http.StatusInternalServerError) + // Возвращаем 200 OK, чтобы Telegram не повторял запрос + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(map[string]interface{}{ + "ok": false, + "error": err.Error(), + "message": "Error processing message", + }) return } @@ -5199,6 +5294,7 @@ func (a *App) telegramWebhookHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]interface{}{ + "ok": true, "message": "Message processed successfully", "result": response, }) diff --git a/play-life-web/package.json b/play-life-web/package.json index 316fbf4..3f15c59 100644 --- a/play-life-web/package.json +++ b/play-life-web/package.json @@ -1,6 +1,6 @@ { "name": "play-life-web", - "version": "2.0.3", + "version": "2.0.4", "type": "module", "scripts": { "dev": "vite", diff --git a/play-life-web/src/components/TodoistIntegration.jsx b/play-life-web/src/components/TodoistIntegration.jsx index 5f7d39c..44644b2 100644 --- a/play-life-web/src/components/TodoistIntegration.jsx +++ b/play-life-web/src/components/TodoistIntegration.jsx @@ -7,6 +7,7 @@ function TodoistIntegration({ onBack }) { const [webhookURL, setWebhookURL] = useState('') const [loading, setLoading] = useState(true) const [copied, setCopied] = useState(false) + const [error, setError] = useState('') useEffect(() => { fetchWebhookURL() @@ -15,14 +16,21 @@ function TodoistIntegration({ onBack }) { const fetchWebhookURL = async () => { try { setLoading(true) + setError('') const response = await authFetch('/api/integrations/todoist/webhook-url') if (!response.ok) { - throw new Error('Ошибка при загрузке URL webhook') + const errorData = await response.json().catch(() => ({})) + throw new Error(errorData.error || 'Ошибка при загрузке URL webhook') } const data = await response.json() - setWebhookURL(data.webhook_url) + if (data.webhook_url) { + setWebhookURL(data.webhook_url) + } else { + throw new Error('Webhook URL не найден в ответе') + } } catch (error) { console.error('Error fetching webhook URL:', error) + setError(error.message || 'Не удалось загрузить webhook URL') } finally { setLoading(false) } @@ -50,6 +58,16 @@ function TodoistIntegration({ onBack }) {

Webhook URL

{loading ? (
Загрузка...
+ ) : error ? ( +
+

{error}

+ +
) : (