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("=== Todoist Webhook Request ===")
|
||||||
log.Printf("Method: %s", r.Method)
|
log.Printf("Method: %s", r.Method)
|
||||||
log.Printf("URL: %s", r.URL.String())
|
log.Printf("URL: %s", r.URL.String())
|
||||||
|
log.Printf("Path: %s", r.URL.Path)
|
||||||
log.Printf("RemoteAddr: %s", r.RemoteAddr)
|
log.Printf("RemoteAddr: %s", r.RemoteAddr)
|
||||||
|
|
||||||
if r.Method == "OPTIONS" {
|
if r.Method == "OPTIONS" {
|
||||||
@@ -4899,9 +4900,16 @@ func (a *App) todoistWebhookHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
// Извлекаем токен из URL
|
// Извлекаем токен из URL
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
token := vars["token"]
|
token := vars["token"]
|
||||||
|
log.Printf("Extracted token from URL: '%s'", token)
|
||||||
if token == "" {
|
if token == "" {
|
||||||
log.Printf("Todoist webhook: missing token in URL")
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4915,11 +4923,23 @@ func (a *App) todoistWebhookHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
log.Printf("Todoist webhook: invalid token: %s", token)
|
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
|
return
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
log.Printf("Error finding user by webhook token: %v", err)
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4929,7 +4949,13 @@ func (a *App) todoistWebhookHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
bodyBytes, err := io.ReadAll(r.Body)
|
bodyBytes, err := io.ReadAll(r.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error reading request body: %v", err)
|
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
|
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))
|
log.Printf("Provided secret in header: %v (length: %d)", providedSecret != "", len(providedSecret))
|
||||||
if providedSecret != todoistWebhookSecret {
|
if providedSecret != todoistWebhookSecret {
|
||||||
log.Printf("Invalid Todoist webhook secret provided (expected length: %d, provided length: %d)", len(todoistWebhookSecret), len(providedSecret))
|
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
|
return
|
||||||
}
|
}
|
||||||
log.Printf("Webhook secret validated successfully")
|
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 {
|
if err := json.NewDecoder(r.Body).Decode(&webhook); err != nil {
|
||||||
log.Printf("Error decoding Todoist webhook: %v", err)
|
log.Printf("Error decoding Todoist webhook: %v", err)
|
||||||
log.Printf("Failed to parse body as JSON: %s", string(bodyBytes))
|
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
|
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("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(" title='%s' (empty: %v), description='%s' (empty: %v)", title, title == "", description, description == "")
|
||||||
log.Printf("Available keys in event_data: %v", getMapKeys(webhook.EventData))
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5038,7 +5082,13 @@ func (a *App) todoistWebhookHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
response, err := a.processMessageWithoutTelegram(combinedText, userIDPtr)
|
response, err := a.processMessageWithoutTelegram(combinedText, userIDPtr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("ERROR processing Todoist message: %v", err)
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5072,14 +5122,22 @@ func (a *App) todoistWebhookHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
log.Printf("=== Todoist Webhook Request Completed Successfully ===")
|
log.Printf("=== Todoist Webhook Request Completed Successfully ===")
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||||
|
"ok": true,
|
||||||
"message": "Task processed successfully",
|
"message": "Task processed successfully",
|
||||||
"result": response,
|
"result": response,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) telegramWebhookHandler(w http.ResponseWriter, r *http.Request) {
|
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" {
|
if r.Method == "OPTIONS" {
|
||||||
|
log.Printf("OPTIONS request, returning OK")
|
||||||
setCORSHeaders(w)
|
setCORSHeaders(w)
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
return
|
return
|
||||||
@@ -5089,9 +5147,16 @@ func (a *App) telegramWebhookHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
// Извлекаем токен из URL
|
// Извлекаем токен из URL
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
token := vars["token"]
|
token := vars["token"]
|
||||||
|
log.Printf("Extracted token from URL: '%s'", token)
|
||||||
if token == "" {
|
if token == "" {
|
||||||
log.Printf("Telegram webhook: missing token in URL")
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5105,11 +5170,24 @@ func (a *App) telegramWebhookHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
log.Printf("Telegram webhook: invalid token: %s", token)
|
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
|
return
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
log.Printf("Error finding user by webhook token: %v", err)
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5119,7 +5197,14 @@ func (a *App) telegramWebhookHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
var update TelegramUpdate
|
var update TelegramUpdate
|
||||||
if err := json.NewDecoder(r.Body).Decode(&update); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(&update); err != nil {
|
||||||
log.Printf("Error decoding Telegram webhook: %v", err)
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5134,7 +5219,9 @@ func (a *App) telegramWebhookHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
} else {
|
} else {
|
||||||
log.Printf("Telegram webhook received: update_id=%d, but no message or edited_message found", update.UpdateID)
|
log.Printf("Telegram webhook received: update_id=%d, but no message or edited_message found", update.UpdateID)
|
||||||
w.Header().Set("Content-Type", "application/json")
|
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",
|
"message": "No message found in update",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
@@ -5173,7 +5260,9 @@ func (a *App) telegramWebhookHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
if 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{
|
w.WriteHeader(http.StatusOK) // Возвращаем 200 OK для Telegram
|
||||||
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||||
|
"ok": true,
|
||||||
"message": "No text in message, ignored",
|
"message": "No text in message, ignored",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
@@ -5191,7 +5280,13 @@ func (a *App) telegramWebhookHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
response, err := a.processTelegramMessage(fullText, entities, userIDPtr)
|
response, err := a.processTelegramMessage(fullText, entities, userIDPtr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error processing Telegram message: %v", err)
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5199,6 +5294,7 @@ func (a *App) telegramWebhookHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||||
|
"ok": true,
|
||||||
"message": "Message processed successfully",
|
"message": "Message processed successfully",
|
||||||
"result": response,
|
"result": response,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "play-life-web",
|
"name": "play-life-web",
|
||||||
"version": "2.0.3",
|
"version": "2.0.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ function TodoistIntegration({ onBack }) {
|
|||||||
const [webhookURL, setWebhookURL] = useState('')
|
const [webhookURL, setWebhookURL] = useState('')
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
const [copied, setCopied] = useState(false)
|
const [copied, setCopied] = useState(false)
|
||||||
|
const [error, setError] = useState('')
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchWebhookURL()
|
fetchWebhookURL()
|
||||||
@@ -15,14 +16,21 @@ function TodoistIntegration({ onBack }) {
|
|||||||
const fetchWebhookURL = async () => {
|
const fetchWebhookURL = async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
|
setError('')
|
||||||
const response = await authFetch('/api/integrations/todoist/webhook-url')
|
const response = await authFetch('/api/integrations/todoist/webhook-url')
|
||||||
if (!response.ok) {
|
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()
|
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) {
|
} catch (error) {
|
||||||
console.error('Error fetching webhook URL:', error)
|
console.error('Error fetching webhook URL:', error)
|
||||||
|
setError(error.message || 'Не удалось загрузить webhook URL')
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
}
|
}
|
||||||
@@ -50,6 +58,16 @@ function TodoistIntegration({ onBack }) {
|
|||||||
<h2 className="text-lg font-semibold mb-4">Webhook URL</h2>
|
<h2 className="text-lg font-semibold mb-4">Webhook URL</h2>
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<div className="text-gray-500">Загрузка...</div>
|
<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">
|
<div className="flex items-center gap-2">
|
||||||
<input
|
<input
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
[supervisord]
|
[supervisord]
|
||||||
nodaemon=true
|
nodaemon=true
|
||||||
logfile=/var/log/supervisor/supervisord.log
|
logfile=/dev/stdout
|
||||||
|
logfile_maxbytes=0
|
||||||
pidfile=/var/run/supervisord.pid
|
pidfile=/var/run/supervisord.pid
|
||||||
user=root
|
user=root
|
||||||
|
|
||||||
@@ -17,8 +18,11 @@ command=/app/backend/main
|
|||||||
directory=/app/backend
|
directory=/app/backend
|
||||||
autostart=true
|
autostart=true
|
||||||
autorestart=true
|
autorestart=true
|
||||||
stderr_logfile=/var/log/supervisor/backend.err.log
|
# Логи идут в stdout/stderr контейнера для docker logs
|
||||||
stdout_logfile=/var/log/supervisor/backend.out.log
|
stderr_logfile=/dev/stderr
|
||||||
|
stdout_logfile=/dev/stdout
|
||||||
|
stderr_logfile_maxbytes=0
|
||||||
|
stdout_logfile_maxbytes=0
|
||||||
priority=20
|
priority=20
|
||||||
# Переменные окружения будут переданы из docker run --env-file
|
# Переменные окружения будут переданы из docker run --env-file
|
||||||
# PORT по умолчанию 8080 внутри контейнера
|
# PORT по умолчанию 8080 внутри контейнера
|
||||||
|
|||||||
Reference in New Issue
Block a user