v2.0.5: Fix transaction errors and webhook parsing
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 40s
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 40s
- Fixed transaction abort error in insertMessageData (replaced ON CONFLICT with SELECT check) - Fixed double body reading in setupTelegramWebhook (use json.Unmarshal) - Fixed Todoist webhook JSON parsing (use json.Unmarshal from bodyBytes) - Improved error handling in webhook responses - Added user_id to nodes insertion
This commit is contained in:
@@ -3280,15 +3280,19 @@ func setupTelegramWebhook(botToken, webhookURL string) error {
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
bodyBytes, _ := io.ReadAll(resp.Body)
|
||||
bodyBytes, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read response body: %w", err)
|
||||
}
|
||||
log.Printf("Telegram API response: status=%d, body=%s", resp.StatusCode, string(bodyBytes))
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("telegram API returned status %d: %s", resp.StatusCode, string(bodyBytes))
|
||||
}
|
||||
|
||||
// Декодируем из уже прочитанных байтов
|
||||
var result map[string]interface{}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
if err := json.Unmarshal(bodyBytes, &result); err != nil {
|
||||
return fmt.Errorf("failed to decode response: %w", err)
|
||||
}
|
||||
|
||||
@@ -3932,34 +3936,55 @@ func (a *App) insertMessageData(entryText string, createdDate string, nodes []Pr
|
||||
// Вставляем проекты
|
||||
for projectName := range projectNames {
|
||||
if userID != nil {
|
||||
_, err := tx.Exec(`
|
||||
INSERT INTO projects (name, deleted, user_id)
|
||||
VALUES ($1, FALSE, $2)
|
||||
ON CONFLICT ON CONSTRAINT unique_project_name DO UPDATE
|
||||
SET name = EXCLUDED.name, deleted = FALSE
|
||||
`, projectName, *userID)
|
||||
if err != nil {
|
||||
// Try without user constraint for backwards compatibility
|
||||
// Используем более универсальный подход: проверяем существование и вставляем/обновляем
|
||||
var existingID int
|
||||
err := tx.QueryRow(`
|
||||
SELECT id FROM projects
|
||||
WHERE name = $1 AND user_id = $2 AND deleted = FALSE
|
||||
`, projectName, *userID).Scan(&existingID)
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
// Проект не существует, создаем новый
|
||||
_, err = tx.Exec(`
|
||||
INSERT INTO projects (name, deleted, user_id)
|
||||
VALUES ($1, FALSE, $2)
|
||||
ON CONFLICT (name) DO UPDATE
|
||||
SET name = EXCLUDED.name, deleted = FALSE, user_id = COALESCE(projects.user_id, EXCLUDED.user_id)
|
||||
`, projectName, *userID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to upsert project %s: %w", projectName, err)
|
||||
// Если ошибка из-за уникальности, пробуем обновить существующий
|
||||
_, err = tx.Exec(`
|
||||
UPDATE projects
|
||||
SET deleted = FALSE, user_id = COALESCE(user_id, $2)
|
||||
WHERE name = $1
|
||||
`, projectName, *userID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to upsert project %s: %w", projectName, err)
|
||||
}
|
||||
}
|
||||
} else if err != nil {
|
||||
return fmt.Errorf("failed to check project %s: %w", projectName, err)
|
||||
}
|
||||
// Проект уже существует, ничего не делаем
|
||||
} else {
|
||||
_, err := tx.Exec(`
|
||||
INSERT INTO projects (name, deleted)
|
||||
VALUES ($1, FALSE)
|
||||
ON CONFLICT (name) DO UPDATE
|
||||
SET name = EXCLUDED.name, deleted = FALSE
|
||||
`, projectName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to upsert project %s: %w", projectName, err)
|
||||
// Для случая без user_id (legacy)
|
||||
var existingID int
|
||||
err := tx.QueryRow(`
|
||||
SELECT id FROM projects
|
||||
WHERE name = $1 AND deleted = FALSE
|
||||
`, projectName).Scan(&existingID)
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
// Проект не существует, создаем новый
|
||||
_, err = tx.Exec(`
|
||||
INSERT INTO projects (name, deleted)
|
||||
VALUES ($1, FALSE)
|
||||
`, projectName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to insert project %s: %w", projectName, err)
|
||||
}
|
||||
} else if err != nil {
|
||||
return fmt.Errorf("failed to check project %s: %w", projectName, err)
|
||||
}
|
||||
// Проект уже существует, ничего не делаем
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3984,12 +4009,37 @@ func (a *App) insertMessageData(entryText string, createdDate string, nodes []Pr
|
||||
|
||||
// 3. Вставляем nodes
|
||||
for _, node := range nodes {
|
||||
_, err := tx.Exec(`
|
||||
INSERT INTO nodes (project_id, entry_id, score)
|
||||
SELECT p.id, $1, $2
|
||||
FROM projects p
|
||||
WHERE p.name = $3 AND p.deleted = FALSE
|
||||
`, entryID, node.Score, node.Project)
|
||||
var projectID int
|
||||
if userID != nil {
|
||||
err = tx.QueryRow(`
|
||||
SELECT id FROM projects
|
||||
WHERE name = $1 AND user_id = $2 AND deleted = FALSE
|
||||
`, node.Project, *userID).Scan(&projectID)
|
||||
} else {
|
||||
err = tx.QueryRow(`
|
||||
SELECT id FROM projects
|
||||
WHERE name = $1 AND deleted = FALSE
|
||||
`, node.Project).Scan(&projectID)
|
||||
}
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
return fmt.Errorf("project %s not found after insert", node.Project)
|
||||
} else if err != nil {
|
||||
return fmt.Errorf("failed to find project %s: %w", node.Project, err)
|
||||
}
|
||||
|
||||
// Вставляем node с user_id
|
||||
if userID != nil {
|
||||
_, err = tx.Exec(`
|
||||
INSERT INTO nodes (project_id, entry_id, score, user_id)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
`, projectID, entryID, node.Score, *userID)
|
||||
} else {
|
||||
_, err = tx.Exec(`
|
||||
INSERT INTO nodes (project_id, entry_id, score)
|
||||
VALUES ($1, $2, $3)
|
||||
`, projectID, entryID, node.Score)
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to insert node for project %s: %w", node.Project, err)
|
||||
}
|
||||
@@ -4963,9 +5013,6 @@ func (a *App) todoistWebhookHandler(w http.ResponseWriter, r *http.Request) {
|
||||
log.Printf("Request body (raw): %s", string(bodyBytes))
|
||||
log.Printf("Request body length: %d bytes", len(bodyBytes))
|
||||
|
||||
// Создаем новый reader из прочитанных байтов для парсинга
|
||||
r.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
|
||||
|
||||
// Опциональная проверка секрета webhook (если задан в переменных окружения)
|
||||
todoistWebhookSecret := getEnv("TODOIST_WEBHOOK_SECRET", "")
|
||||
log.Printf("Webhook secret check: configured=%v", todoistWebhookSecret != "")
|
||||
@@ -4986,9 +5033,9 @@ func (a *App) todoistWebhookHandler(w http.ResponseWriter, r *http.Request) {
|
||||
log.Printf("Webhook secret validated successfully")
|
||||
}
|
||||
|
||||
// Парсим webhook от Todoist
|
||||
// Парсим webhook от Todoist из уже прочитанных байтов
|
||||
var webhook TodoistWebhook
|
||||
if err := json.NewDecoder(r.Body).Decode(&webhook); err != nil {
|
||||
if err := json.Unmarshal(bodyBytes, &webhook); err != nil {
|
||||
log.Printf("Error decoding Todoist webhook: %v", err)
|
||||
log.Printf("Failed to parse body as JSON: %s", string(bodyBytes))
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
@@ -5015,7 +5062,9 @@ func (a *App) todoistWebhookHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if webhook.EventName != "item:completed" {
|
||||
log.Printf("Received Todoist event '%s', ignoring (only processing 'item:completed')", webhook.EventName)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]string{
|
||||
w.WriteHeader(http.StatusOK)
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"ok": true,
|
||||
"message": "Event ignored",
|
||||
"event": webhook.EventName,
|
||||
})
|
||||
@@ -5097,7 +5146,9 @@ func (a *App) todoistWebhookHandler(w http.ResponseWriter, r *http.Request) {
|
||||
log.Printf("Todoist webhook: no nodes found in message, ignoring (not saving to database and not sending to Telegram)")
|
||||
log.Printf("=== Todoist Webhook Request Ignored (No Nodes) ===")
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"ok": true,
|
||||
"message": "Message ignored (no nodes found)",
|
||||
"ignored": true,
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user