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
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:
@@ -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,
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "play-life-web",
|
||||
"version": "2.0.3",
|
||||
"version": "2.0.4",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -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()
|
||||
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 }) {
|
||||
<h2 className="text-lg font-semibold mb-4">Webhook URL</h2>
|
||||
{loading ? (
|
||||
<div className="text-gray-500">Загрузка...</div>
|
||||
) : error ? (
|
||||
<div className="text-red-600 mb-4">
|
||||
<p className="mb-2">{error}</p>
|
||||
<button
|
||||
onClick={fetchWebhookURL}
|
||||
className="px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition-colors"
|
||||
>
|
||||
Попробовать снова
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
[supervisord]
|
||||
nodaemon=true
|
||||
logfile=/var/log/supervisor/supervisord.log
|
||||
logfile=/dev/stdout
|
||||
logfile_maxbytes=0
|
||||
pidfile=/var/run/supervisord.pid
|
||||
user=root
|
||||
|
||||
@@ -17,8 +18,11 @@ command=/app/backend/main
|
||||
directory=/app/backend
|
||||
autostart=true
|
||||
autorestart=true
|
||||
stderr_logfile=/var/log/supervisor/backend.err.log
|
||||
stdout_logfile=/var/log/supervisor/backend.out.log
|
||||
# Логи идут в stdout/stderr контейнера для docker logs
|
||||
stderr_logfile=/dev/stderr
|
||||
stdout_logfile=/dev/stdout
|
||||
stderr_logfile_maxbytes=0
|
||||
stdout_logfile_maxbytes=0
|
||||
priority=20
|
||||
# Переменные окружения будут переданы из docker run --env-file
|
||||
# PORT по умолчанию 8080 внутри контейнера
|
||||
|
||||
Reference in New Issue
Block a user