v2.0.4: Fix webhook error handling and logging
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 41s

- 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
This commit is contained in:
poignatov
2026-01-01 18:50:55 +03:00
parent 7704de334c
commit edc29fbd97
5 changed files with 140 additions and 22 deletions

View File

@@ -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,
})